クロージャってなんだろう
某所でSICPのことを聞かれて、クロージャとは何者かと聞かれて
概要は説明できたが非常に機微な部分の説明が怪しかったので
自分への復習の意味も兼ねてクロージャに再入門してみます。
まず、クロージャというのは簡単にいうと、
- 関数が定義されたときの環境
- その関数自体
がセットになったデータ構造です。
今回扱う言語はSICPでも扱っているLispの方言
schemeです。
common lispとの違いはいろいろありますが、とりあえずは
defun -> defineになっているくらいで取り合えず今回の内容は理解できるはず。
まず、クロージャを作る方法
これは簡単です。
(lambda (x) x)
lambdaで囲われた世界がクロージャです。
とここまでは簡単なのですが
SICPで扱っている例は若干複雑です。
(define new-withdraw (let ((balance 100)) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")))) (define (make-withdraw balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")))
こういう定義がされている場合に、ほとんど同じように見えるのですが
大きく違います。
(define W1 (make-withdraw 100)) (define W2 (make-withdraw 100)) (W1 50) 50 (W2 70) 30 (W2 40) "Insufficient funds" (W1 40) 10
このように、make-withdrawbalanceでは、実行するたび新しいクロージャを作って返してくれます。
しかし、new-withdrawではそのようにはなりません。
(new-withdraw 10) 90 (new-withdraw 20) 70
このように同じクロージャをnew-withdrawでは返します。
これは何が違ってこのようになったのか、よく考えないと分かりません。
ここでdefineおよびletをlambda式を使った同一の式にすると理解の助けになると思います。
(define new-withdraw ((lambda (balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))))) 100) (define make-withdraw (lambda (balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))))
このようにしてみると、両者の違いが歴然としてきます。
(define new-withdraw .......)では
*1がmake-withdrawに束縛されています。
これは引数をひとつ必要とするlambda式で(make-withdraw 100)などとしてやっと始めて
クロージャが実際に作られるものです。
なので呼ばれるたびに違うクロージャが作られるという結果になります。
SICPは面白い本なのですが、内容が濃くなってて一文も見逃さないように
本当に注意深く読まないといけないですね。
ちゃんちゃん。
*1:lambda (balance) (......) 100)と定義されているクロージャに new-withdrawが束縛されています。 ですので、呼ばれるたびにまったく同じ場所を参照するわけです。 (define make-withdraw....)では (lambda (balance) (....)