lispのスペシャル変数
id:bowbow99:20070426:1177521127 さんところの、スペシャル変数とクロージャの動作が理解できなかったので、自分でも試してみた。
$ clisp [1]> (defun make-counter () (let ((n 0)) #'(lambda () (incf n)))) make-counter [2]> (setq counter (make-counter)) #<function :lambda nil (incf n)> [3]> (funcall counter) 1 [4]> (funcall counter) 2 [5]> (defvar n 100) n [6]> (funcall counter) 101
エエーー?
スペシャル宣言された変数恐るべし。クロージャも破壊されてしまうということか。なんか萎える。
スペシャル宣言された変数というものを理解できていないんでしょう、多分。
クロージャ生成の関数をcompileすれば破壊されなくなりました。下の方の「追記20070525」 を参照
スペシャル宣言された変数はダイナミックスコープということでした。
理解が乏しくて同じことができているのかわからないけど、perlでは、myで宣言した変数はレキシカルスコープ、localで宣言した変数はダイナミックスコープになるはずのなで、↓のようになると思う。
#!/usr/bin/perl #都合上use strict も use warningsもしない sub make_counter { my $n = 0; return sub {++$n}; } local $n = 100; my $counter = make_counter(); print &{$counter}, "\n"; print &{$counter}, "\n"; $n = 200; print &{$counter}, "\n"; print &{$counter}, "\n";
の結果は、「1 2 3 4」が出力される。perlでは、クロージャの中で参照されるのは、レキシカルスコープの$nで、ダイナミックスコープの$nではない。perlのスコープの方が自然に感じるのは、両方の言語の知識不足で、同じことができていないからかも(結構自信ない)
lispでは、レキシカルスコープの変数より先にダイナミックスコープの変数(スペシャル宣言された変数)が参照されるということか。
いろいろ解ってないので、setq からやりなおします。
追記20070522
http://www.ice.nuie.nagoya-u.ac.jp/~h003149b/lang/comparison.html にスコープについての詳しい説明がありました。
動的スコープでは、変数がどのスコープの内側にあるのかは実際の実行時に決まる。単なる局所変数のつもりだったものが他の関数の結果に影響をあたえ得るというのは、多少気持ち悪いかもしれない。でも普通、大域変数にはそれらしい名前をつける (「*」ではじまり「*」で終る名前にするとか)ので、こういう場合はそれほど問題にはならないと思う。
http://www.ice.nuie.nagoya-u.ac.jp/~h003149b/lang/comparison.html
やっぱり、この辺は正しく理解して、大域変数を意識して使用することが必要なんですね。
追記20070525
通りすがりさんに、(defun make-counter …) の直後に (compile ’make-counter) を入れると期待とおりの結果が得られるということを教えて頂きました。
(defvar n 100)した後も、クロージャが破壊される現象はなくなりました。
HyperSpec難しい。。。
(defun make-counter …) の直後に (compile ’make-counter) としてみてください。期待通りの結果が得られるはずです。
これは結構やっかいな話で、詳しくは
http://www.lispworks.com/documentation/HyperSpec/Body/03_bbc.htm
を参照していただくとして、とりあえず Common Lisp で colsure を使う場合はコンパイルしておいた方が安全です。
通りすがりさん、コメントありがとうございます。
教えて頂いたように(compile ’make-counter) を入れたところ、クロージャが破壊されるような現象は起きませんでした。(予想通りの動作になりました)
スコープ難しいです。HyperSpec読んで考えてみます。ありがとうございました。