関数内で使われている変数を一時的に変えたい場合があると思います。
バッドノウハウかもしれませんが、個人用の設定にはとても有用です。
Dynamic scope
(setq foo 1)
(defun bar1 () foo)
(let ((foo 2))
foo ; => 2
(bar1) ; => 2
)
foo ; => 1
(bar1) ; => 1
一時的に関数bar
内の変数foo
がグローバル変数foo
ではなく、let
で定義したfoo
を見るようになりました。
しかし、これはダイナミックスコープ時のみ有効ですので、レキシカルスコープの場合には以下のようになってしまいます。
Lexical scope
;;; -*- lexical-binding: t -*-
(setq foo 1)
(defun bar1 () foo)
(let ((foo 2))
foo ; => 2
(bar1) ; => 1
)
foo ; => 1
(bar1) ; => 1
レキシカルスコープでは関数bar1
内の変数foo
はグローバル変数foo
を見ています。
(1行目に特殊コメントを書いておくとレキシカルスコープになります。)
Override Inner variables safely
;;; -*- lexical-binding: t -*-
(setq foo 1)
(defun bar1 () foo)
(let ((_foo foo))
(unwind-protect
(progn
(setq foo 2)
foo ; => 2
(bar1) ; => 2
)
(setq foo _foo))
)
foo ; => 1
(bar1) ; => 1
上記の方法ならダイナミック/レキシカル両方のスコープでセーフですね。
一時的にグローバル変数foo
の値を変更して、最後にunwind-protect
によって確実に元の値に戻しています。
Macro
;;; -*- coding: utf-8; lexical-binding: t -*-
(require 'cl-lib)
(defmacro overriding-let (bindings &rest body)
(declare (indent 1))
(let (original-pairs
local-pairs
overriding-targets
(bin `,bindings))
(cl-dotimes (x (length bin))
(let ((b (nth x bin))
s v)
(cl-typecase b
(cons (setq s (or (ignore-errors (car b)) b))
(if (boundp s)
(progn
(setq v (symbol-value s))
(setq original-pairs (cons (cons s v) original-pairs))
(setq overriding-targets (cons b overriding-targets)))
(setq local-pairs (cons b local-pairs))))
(symbol
(setq local-pairs (cons b local-pairs)))
(t (error "Binding format error")))))
`(let ,local-pairs
(unwind-protect
(progn
(mapc (lambda (x)
(let ((s (intern (symbol-name (car x))))
(v (car (cdr x))))
(set s v)))
',overriding-targets)
,@body)
(mapc (lambda (x)
(let ((s (car x))
(v (cdr x)))
(set s v)))
',original-pairs)))))
(setq foo1 1)
(setq foo2 9)
(defun bar1 () foo1)
(defun bar2 () foo2)
(overriding-let ((foo1 2)
(foo2 99)
(foo3 30) ;; グローバル変数ではないのでローカル変数として扱う
foo4) ;; グローバル変数ではないのでローカル変数として扱う (宣言のみ)
(bar1) ; => 2
(bar2) ; => 99
foo1 ; => 2
foo2 ; => 99
foo3 ; => 30
foo4 ; => nil
)
(bar1) ; => 1
(bar2) ; => 9
foo1 ; => 1
foo2 ; => 9
(ignore-errors foo3) ; => nil
(ignore-errors foo4) ; => nil
マクロ化して設定に書いておくと、面倒な記述を減らしてスマートになりますね。