置き換えモデルでは、副作用がある式を扱えない場合があるらしい。
まず「副作用」を定義しなくちゃいけないだろうけど、自分もちゃんと理解できていないので定義せずスルーする。ごめんなさい。
では、置き換えモデルで
(((lambda (y) (lambda (x) (begin (set! y (+ x y)) y))) 10) 1)
の評価過程を追ってみる。
...っと、その前に、lambdaとbeginとset!は手続きではなく「構文」というもので、作用順序評価というルールに従わないものであることを前置きしておく。
構文に関しては、たぶん本の後の方でに出てくると思うので、詳しくはその時に書く(かもしれない)。
さて、では気を取り直して、評価過程を追っていく。リストを評価しようとしているので、作用順序評価より、全引数の評価と手続きの作成が行われる。引数はプリミティブな整数1。手続きは複雑に入り組んでるので、手続きを作らないといけない。便宜的にこの手続きをfと名付けておく(脳内で勝手に名前をつけておくという意味で、defineするという意味ではない)。するとfは
((lambda (y) (lambda (x) (begin (set! y (+ x y)) y))) 10)
となる。fを評価すると、ちょっと簡単になった手続きfができるね。
でもfもまた関数適用の形。引数は10で、手続きは...またややこしいなぁ。
...って、ややこしく見えるけど、リストの先頭要素がlambdaの場合には関数適用ではない。この場合の構文は「ラムダ抽象」にあたり、このリストは値としての手続きへと評価される。つまり、
(lambda (y) (lambda (x) (begin (set! y (+ x y)) y)))
はこれ以上置き換えられない式で、これと引数の10でもって関数適用すると手続きができる。
yを10へと置き換えてやればいいのだが、先のことを考えるとちょっと困ったことがある。beginは引数として与えられたS式を、上から順に評価していく構文。(set! ...)の後にyが評価され、最後の引数が(begin ...)の結果として返される。ここで、単純にyを置き換えてしまうと
(lambda (x) (begin (set! 10 (+ x 10)) 10))
ってなってしまう。期待される動きは、set!でyが更新されて、更新後のyが返されること。さらに、set!の意味から考えると明らかにおかしい。整数10に代入とかありえんし。
...って、あれ?本の解説と全然違うなぁ。本のコード例では、yを1で置き換えてるっぽい?しかもset!の第1引数がyのままだ。なんで?作用順序評価になってなくない?
うーむ...、よく分からないけど、「置き換えモデルでは副作用を正しく扱えない」ということを例示するためのもので、元々うまくいかない例なので、これで良しとする。
でも...
beginやset!は構文であり手続きではないので、以下のように独自の評価ルールを持っている。
- 引数が全て評価されるとは限らない
- 評価される引数の評価順序
これらは構文ごとに異なるので、これらを踏まえて、beginの第2引数とset!の第1引数を評価せずに置いておくとすると
(lambda (x) (begin (set! y (+ x 10)) y))
となるから、さらにxを1で置き換えてやって、
(begin (set! y (+ 1 10)) y)
ってなるから、
- set!で、yを束縛している値10を(+ 1 10)に変更する
- 書き換えられた環境でもってyを評価して、その値11を返す
ってやると、正しく11が返るんジャマイカ?
「置き換えずに、いったん保留しておく」ってやる時点ですでに置き換えモデルとは呼べなくなるのかな。