2010-04-19

Closure (クロージャー) って何だろう?

何年か前に 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 言語は構造体に関数を足したもの、クロージャは関数に変数を足したもの」というのを見た記憶がある。当時はよく分からなかったけど、今なら少し分かる気がする。

ANSI Common Lisp (スタンダードテキスト)実践Common Lisp

No comments:

Post a Comment