Emacs Lisp の dynamic binding と lexical binding

GNU Emacs をカスタマイズするのに使われる Emacs Lispはdynamic bindingとlexical bindingという二種類のbindingを提供しているので、それらを比較してみます。dynamic bindingとlexical binding自体やclosureなどは既知であることを想定しています。

Dynamic binding

デフォルトでは、Emacs Lispは変数に対してdynamic bindingを使います。dynamic bindingでは、変数の値はlambda式が定義された時点での環境ではなく、lambda式が呼び出された時点での変数環境が使われます。以下にdynamic bindingの例を示します。

;; lexical bindingを無効化してdynamic bindingを使う
(setq lexical-binding nil)
;> nil

;; ラムダ関数を返す関数を定義する
(defun retcl (x)
  (lambda () (+ x 1)))
;> retcl

;; 引数10で関数を呼び出す
(setq f (retcl 10))
;> (lambda nil (+ x 1))

;; xが20であるコンテキスト内で関数fを呼び出す。retcl内の変数環境ではなく呼出時の変数環境が使われるので、結果は21になる
(let ((x 20))
  (funcall f))
;> 21

この例では、~(let ((x 20)) …)~ ブロック内で (funcall f) を呼び出すと結果は 21 となります。以下にdynamic bindingの例を示します。これは、動的束縛では、変数 x の値が関数呼び出し時の環境から取得されるためです。この場合、それは 20 で、ラムダ関数内で 1 が加えられて 21 となります。

Lexical binding

一方、lexical bindingは、関数が定義された時点での変数環境を使い続けます。以下にlexical bindingの例を示します。

;; lexical bindingを有効化して、同じ例を動かす
(setq lexical-binding t)
;> t

;; ラムダ関数を返す関数を定義する
(defun retcl (x)
  (lambda () (+ x 1)))
;> retcl

;; 引数10で関数を呼び出す。そもそもこの時点で (x . 10) という環境の元でのclosureということが表示されているが…
(setq f (retcl 10))
;> (closure ((x . 10) t) nil (+ x 1))

;; xが20であるコンテキスト内で関数fを呼び出す。呼出時の変数環境ではなくretcl内の変数環境が使われるので、結果は11になる
(let ((x 20))
  (funcall f))
;> 11

この場合、出力は 11 となります。これは、lexical bindingにおいて、ラムダ関数 (+ x 1) が定義されたときの環境、つまり x の値が 10 である環境を保持しているためです。したがって、~(let ((x 20)) …)~ ブロックで x20 と定義されていても、ラムダ関数内の x は関数 f が定義されたときの 10 を参照しています。