lexical-binding

Emacs 24 が release されて直後に途中まで書いてからほったらかしておいたら、大部経ってしまった。
前半あたりはたぶん 24.1 最後あたりは 24.3 でやった結果。


Emacs の lexical scope について、いくつか思いつくところを試していきます。

(symbol-plist 'lexical-binding)
=> (safe-local-variable booleanp variable-documentation 585535)
(symbol-function 'lexical-binding)
=> Symbol's function definition is void: lexical-binding
(symbol-value 'lexical-binding)
=> nil
(local-variable-p 'lexical-binding)
=> nil
(setq lexical-binding t)
(local-variable-p 'lexical-binding)

=> t

なので lexical-binding は

(make-variable-buffer-local 'lexical-binding) 

された感じの変数。

lexical-binding が non-nil のバッファで lambda 式を評価すると closure になる。(みたい)

(let ((b 2)) 
  (let ((f (lambda (a) (+ a b))))
    (funcall f 1)))
=> 3

↑これはよくある例

(let ((b 2)) 
  ((lambda (a) (+ a b)) 1))

=> (void-variable b)
|<

↑これが予想外の結果だった

同様のことをやるとしたら、こんな感じになるかな。上のと同様であんまり意味がない例だけど。

>|lisp|
((closure ((b . 2) . t) (a) (+ a b)) 1)
=> 3

んーと。lambda が右辺値として評価された後じゃないと closure にならないってことかな?

(fset 'hoge (let ((a 1)) (lambda (b) (+ b (incf a)))))
(hoge 20)

=> 22

(hoge 20)

=> 23
(symbol-function 'hoge)

=> (closure ((a . 3) t) (b) (+ b (incf a)))

elisp での `closure' とはなんぞ?

(symbol-plist 'closure)

=> nil

(symbol-function 'closure)

=> Symbol's function definition is void: closure

(closure ((a . 3) t) (b) (+ b (incf a)))

=> Symbol's function definition is void: closure

((closure ((a . 3) t) (b) (+ b (incf a))))

=> Wrong number of arguments: (((a . 3) t) (b) (+ b (incf a))), 0

おそらく何の変哲もない symbol だと思う。

((closure ((a . 3) t) (b) (+ b (incf a))) 20)

=> 24

((closure ((a . 3) t) (b) (+ b (incf a))) 20)

=> 24

(closure (lexbind-variables ???) 引数 関数の実体) みたいな感じ?

lexbind-variables を変えてみる。

((closure ((a . 300) t) (b) (+ b (incf a))) 20)

=> 321

??? の部分 (この場合は t) はなんなんだろう? nil に変えてみる。

((closure ((a . 3) nil) (b) (+ b (incf a))) 20)

=> 24

結果だけみると t のときとかわらん。

しかたないので後でソースみる


既存の dynamic binding な仕組みとの関係。

(defvar hogehoge 3)

(let ((hogehoge 5))
  (let ((f (lambda (a) (+ a hogehoge))))
    (funcall f 1)))
=> 6
(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  (funcall hogelambda 1))

=> 4

hogelambda
=> (closure (t) (a) (+ a hogehoge))

hogehoge が closure の変数からなくなってしまう。defvar されてるからだろうな。

(makunbound 'hogehoge)

(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  (funcall hogelambda 1))

=> Symbol's value as variable is void: hogehoge

(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  hogelambda)

(closure ((hogelambda closure #1 (a) (+ a hogehoge)) t) (a) (+ a hogehoge))

ん?変数セルを unbound しても closure の変数に入らない。
と、いうことは intern してあるだけで lexical scope にならないってことかな?

(unintern "hogehoge")
(intern "hogehoge")

(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  (funcall hogelambda 1))

=> Symbol's value as variable is void: hogehoge

だめだ。違う。

さて、試行してる最中に不審な挙動に気付いた。 emacs -q でも試したから間違いない。

↓これだけ書いた lexical-binding t なバッファを作りましょう。
(unintern "hogehoge") しときましょう。`defvar' の form は評価しないでください。

(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  (funcall hogelambda 1))

;; これは評価しない
(defvar hogehoge 3)

(let (hogelambda)
  (let ((hogehoge 5))
    (setq hogelambda (lambda (a) (+ a hogehoge))))
  (funcall hogelambda 1))

一つ目と二つ目の let フォームの内容は同じですが結果は異なります。
これは酷い。これ知らないで lexical scope な elisp 書き散らしてるときは、はまることもありそう。
defvar form 評価後なら問題ないので、さすがにはまることは稀だろうけど、頭の片隅には入れておきたい。
試した限りでは defvar じゃなくても defcustom, defconst あたりは同じみたい。なかなかベタですな。

で、えーと、まとめると、評価する form の前に、未評価な defvar 系 form + 該当する symbol があるときは lexical scope な変数とならない。 ということだろうか。
text 検索してるだけのような気がするので defvar が if の中にあるときとかどうなるのだろう。文字列の中にあるときは?

そういえば、この挙動については随分前にどこかで読んだような気もする。頭の片隅にも入ってなかったやん。


長くなってきて息切れしたので続きはまた今度。あるいは書き足すかも。

TODO: closure form の中にある引数の意味。
TODO: defvar form の中はどこまで評価されるのか。string の中だとどうなる? (eval-sexp-add-defvars)
TODO: C-x C-e のときだけかもしれない。
TODO: lambda 式の定義。 lexical-binding のときはどこにいく?