クロージャとは
http://d.hatena.ne.jp/smeghead/20071216/closure の続きというか今回はもうすこし真面目に
あえて、コードレスで、自分の理解しているところのクロージャを説明してみるテストです。
まず、誤解を恐れずに書いてしまうと、言語によってクロージャという機能が提供されている訳ではない。クロージャというのは、ある機能の副作用を用いたテクニックの名前である。
クロージャと呼ばれているテクニックを使える言語は、以下の機能を提供しています。言い換えると、以下の機能の副作用によってクロージャというテクニックを使用できるようになります。
- レキシカルスコープ(静的スコープ)
- 関数がファーストクラスオブジェクト(第一級オブジェクト)であること
レキシカルスコープ
オブジェクト(主に関数)が定義された時に、その場所から参照できる範囲を、オブジェクト(主に関数)の実行時にも参照できること、また、その範囲をレキシカルスコープと呼びます。レキシカルスコープ内の情報を保持しているところを、レキシカル環境と呼んだりします。
関数がファーストクラスオブジェクトであること
ここでは、以下のことが可能であることを指しています。これは特に説明は要らないと思います。
- 関数を別の関数の戻り値に指定できること
- 関数を変数に代入することができること
クロージャのからくり
- レキシカル環境の中で定義された関数は、関数がガベージコレクションによって破棄されるまでの間、そのレキシカル環境の中にあるオブジェクトを参照することができます。(言語によってレキシカルスコープが提供されているから)
- レキシカル環境の中で定義された関数を、戻り値などとして外の環境の変数などに代入することができます。(関数がファーストクラスオブジェクトであるため)
- この結果、当然のこととして外の環境の変数などに代入された関数の実行時に、関数からレキシカル環境のオブジェクトにアクセスできるようになります。
この現象を利用することが、クロージャと呼ばれているテクニックです。
こう理解すると、クロージャ自体が奇妙なのもなのではなく、奇妙なのはレキシカルスコープであることがわかります。
締め
レキシカルスコープありき。というのが今の自分の理解しているところです。だから、クロージャを説明している文章で、レキシカルという言葉が出てこないのは、誤魔化そうとしているか、その人が誤魔化されてるかのどっちかだと思っています。
レキシカルスコープを理解する良い方法としては、レキシカルスコープとダイナミックスコープの両方を提供している言語で、色々実験することです。(と言ってまた逃げます)
perl、 Common Lispは、レキシカルスコープとダイナミックスコープの両方を提供しています。
追記: id:rayfillさんに突っ込み貰いました。Common Lispでのダイナミックスコープについて。コメント参照。スコープの実験は、perlでやるのが無難かもしれません
クロージャを知ってる人が、あんまり、クロージャ、クロージャ言うのは、有害なのではないかと思いました。
クロージャのわかりにくさ
言ってる側から、自分でクロージャクロージャ言ってしまいました。
common lispのダイナミックスコープは提供しているというよりmacroの副作用じゃ?
emacs lispなんかだとダイナミッククロージャですけど・・・
rayfill さん、こんにちは。
ダイナミッククロージャという言葉を知りませんでした。まずダイナミッククロージャを調べてきます。その後に、common lispのダイナミックスコープについて考えてみます。
ダイナミッククロージャという言葉を調べてみましたが、理解できませんでした。(スコープまわりの用語が、人によって定義が違っていたりする感じですね。。。)
ダイナミッククロージャのことは置いといて、Common Lispがダイナミックスコープを提供しているかどうかという点で、返事をしてみます。
rayfill さんから突っ込みもらって、Common Lisp でのダイナミックスコープの理解が、不十分であることに気がつきました。defvar とか defparameter で定義するとダイナミックスコープになるということ以外のことはよくわかっていませんでした。defvar とか defparameter では、グローバル変数になってしまうので、ダイナミックスコープであることを利用して何かをするのに、あまり意味のある使い道を思いつきません。
http://www.lisp.org/HyperSpec/Body/dec_special.html#special
イマイチ理解しきてれいないですが、(declare (special x))することで、ローカル変数をダイナミックスコープで定義できるそうです。
clispで確認しました。
(let ((x ”lexical scope の x.”))
;x を表示する関数
(defun print-lexical-x ()
(print x)))
(print-lexical-x) ; ■ここでレキシカルスコープのxを表示してみる。
;=> ”lexical scope の x.”
;x を表示する関数
(defun test-dynamic ()
(let ((x ”dynamic scope の x.”))
(declare (special x))
(print-dynamic-x)))
(defun print-dynamic-x ()
(print x)) ; 呼び出し元の実行時の環境の x を表示
(test-dynamic) ; ■ここでダイナミックスコープの x を表示してみる。
;=> ”dynamic scope の x.”
(print-dynamic-x) ; ■ここでダイナミックスコープにxが存在しない状態で、無理矢理 x を表示してみる。
;=> *** – EVAL: variable X has no value
ダイナミックスコープもそれらしく動いているように見えますが、確信が持てない状態です。
中途半端ですみません。
あー、寝ぼけてタイプしてたみたいです。
ダイナミッククロージャじゃなくてダイナミックスコープですね・・・
>中途半端
うちも規格書とか読んでるわけじゃないんで中途半端です(ぉ
そもそもcommon lispで読んだ本って
ポールグラハムのANSI Common LispとOnLispぐらいしか・・・
自分のわかりにくい説明なんかよりちゃんとわかってて書いてる人のを・・・(笑)
http://www.fireproject.jp/feature/common-lisp/details/function.html#5
(declare (sepcial …
はspecial変数の参照をしろ、という宣言のようです。
でspecial変数はダイナミックスコープを持つので文脈依存で値が変わる、ということのようです。ただ結果としてダイナミックスコープなのかよくわからんのですがspecial変数の扱いなどに関してお作法がなんかあるようでやれdefvar つかえとかいろいろ決まりがあるようでその辺はよくわかってません。
またmacroを使うことでダイナミックスコープ自体は以下のように再現できるわけで・・・
(let ((x 0))
(defmacro print-x-macro ()
`(format t ”~A~%” x))
(defun print-x-func ()
(format t ”~A~%” x)))
(let ((x 100))
(print-x-macro)
(print-x-func))
100
0
macroの場合、評価される時点で展開されるのでその場から参照される最も内側の変数を束縛します。結果的にダイナミックスコープ相当の動作をする、というわけです。
で、普通だとこういう動作は予期しない変数を束縛したりしてバグの元にもなったりするんですが(なので普通はmacro内で変数を用意する場合gensymを使います)、逆にダイナミックスコープを利用して継続をcommon lispで実装してみる、という例がOnLispに載ってます。
http://www.komaba.utmc.or.jp/~flatline/onlispjhtml/continuations.html
正直なところ、エレガントとかいうよりも気が狂ってるとしか思えないような仕組みでしたが読んで内容を理解していくと目から鱗がダース単位で落ちる内容でした(笑)