何年か前に Closure って何? って質問を受けた。当時、ぼくはその質問に答えることは出来なかった。今も出来る自信がない。だから少し勉強してみた。
初歩の初歩を噛じっただけだけど、考えをまとめるためにエントリーにしてみる。
参考文献
Closure と言えば Lisp ということで、Common Lisp の本を参考にした。
- 「ANSI Common Lisp」(Paul Graham) pp.96-99
- 「実践 Common Lisp」(Peter Seibel) pp.66-67
Closure
クロージャは「関数と環境を一緒にしたもの」。これがクロージャーの説明として一番良く聞く文言。
クロージャを知っている人にはこれで十分なのでせう。でも、ぼくにはこの説明はまるで霞を掴むように思える。
更に、「ANSI Common Lisp」から引用する。
関数が外部で定義された変数を参照するとき、その変数をフリー変数と呼ぶ。フリー・レキシカル変数を参照する関数はクロージャ (closure) と呼ばれる。そこでは関数が有効である限り、変数も有効であり続ける。
まだ良く分からない。でも一つ分かった。クロージャは関数だということ。その関数は、フリー・レキシカル変数を参照している。
レキシカル変数とは何か? 今度は「実践 Common Lisp」を引用してみる。
レキシカルなスコープを持つ変数は、束縛フォームの内部にあるコードだけが参照できる。Java、C、Perl、Python にはレキシカルなスコープを持つ「ローカル変数」があるので、こういった言語でプログラムした経験があるならレキシカルスコープにもなじみが深いはずだ。
レキシカル・スコープとは何ということはない。ありふれたスコープのこと。C 言語なんかで「ローカル変数は括弧の中でしか生きられない」とさんざ言っている。難しいことじゃなかった。レキシカル・スコープという名前に馴染みがなかったというだけの話。
クロージャはレキシカル・スコープな変数を参照するけど、その変数は関数の外部で定義されている。
ここで、「ANSI Common Lisp」のサンプル・コードを見てみる。
(let ((counter 0)) (defun reset () (setf counter 0)) (defun stamp () (setf counter (+ counter 1))))
関数 reset と stamp が定義されている。この 2 つの関数は、関数の外側の let 式で定義された変数 counter を参照している。なるほど、確かに counter という変数は let 内のレキシカル・スコープに収まっている。その counter を、関数外部の変数として関数 reset と stamp は呼んでいる。この時、関数 reset と stamp はクロージャになる。クロージャが関数と環境 (この場合、外の変数を指しているのだよね?) を一緒にしたもの、というのも何となく分かる気がする。
せっかくなので、stamp と reset を呼んでみやう。こんな感じに使うことができる。
> (list (stamp) (stamp) (reset) (stamp)) (1 2 0 1)
「ANSI Common Lisp」は「同じことは大域カウンターを使ってもできるが、上のようにすると、カウンタが不注意による書き換えから保護される」と続ける。なるほど。奥が深い。
Ruby で書いてみる
同じコードを Ruby で書いてみる。
class Counter def initialize @count = 0 end def stamp return @count += 1 end def reset return @count = 0 end end c = Counter.new() p c.stamp p c.stamp p c.reset p c.stamp
実行例はこちら。
$ ruby count.rb 1 2 0 1
Object 型の言語を見て思うのは、まずメンバー変数ありきで、そこにメソッド (関数) が付いているということ。昔、どこかで「Object 言語は構造体に関数を足したもの、クロージャは関数に変数を足したもの」というのを見た記憶がある。当時はよく分からなかったけど、今なら少し分かる気がする。
No comments:
Post a Comment