置き換えモデルと副作用

置き換えモデルでは、副作用がある式を扱えない場合があるらしい。

まず「副作用」を定義しなくちゃいけないだろうけど、自分もちゃんと理解できていないので定義せずスルーする。ごめんなさい。

では、置き換えモデルで

(((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)

ってなるから、

  1. set!で、yを束縛している値10を(+ 1 10)に変更する
  2. 書き換えられた環境でもってyを評価して、その値11を返す

ってやると、正しく11が返るんジャマイカ

「置き換えずに、いったん保留しておく」ってやる時点ですでに置き換えモデルとは呼べなくなるのかな。