前回の続き。
2.6 Iswimでの呼び出しと束縛
手続き的解釈の適用には注意が必要である。
というのも、そうでなければ、スコープ規則に関して間違いが生じる可能性があるからだ。
特に、グローバル変数を含む関数定義の正しい解釈について注意する必要がある。
関数定義の右辺には、仮引数ではなく、右辺の中で関連する定義がない変数が出現する場合がある。
以下のやのように使用される変数は、関数定義のグローバル(グローバル変数)と呼ばれる:
f(X, Y) = X + B * Y + C;
グローバル変数を伴った「手続き定義」を言語が許可するときは、グローバルが意味(=値)を持つコンテキストでの関数呼び出しの実行において、グローバルの意味(=値)に微妙な疑問が生じる。
たとえば、
f(3, U) where B = 7; C = 5; f(X, Y) = X + B * Y + C; U = P - Q where Q = 2; B = 3; P = f(B, Q); end end
という(where
節の)内側でのの「呼び出し」について考えてみる。
この結果はどうあるべきだろうか?
の値は何になるだろうか?
上記のIswimの「逐次的な」解釈に単純に従えば、単にとに3と2を割り当て、という式を評価するだけである。
ところで、評価を開始するとき、の値は何になっているのだろうか?
変数には、この呼び出しが実行される直前に3が割り当てられている。
の値はそのまま3であり、したがって呼び出しの結果が、すなわち14であると予想するのは論理的だろう。
一方、(外側のwhere
節の)主題での呼び出しが実行されると、(外側のwhere
節の)本体の最初の文の実行後にの値は7になっていることが予想される。
これを少し計算すると、最終的に得られる値は92であると予想される。
(※)
しかし、この予想は間違っている。
Iswimの形式的意味論は、この節の値が148であることを示す。
これは、内側のの呼び出し中にが(3ではなく)7を割り当てられていると仮定すると、得られる値である。
(※なので、)
この例は、Iswimで従来の手続き的な解釈を用いるときの1つの落とし穴を示している。
Iswimの形式的意味論は、言語が「静的束縛(static binding)」と呼ばれるものを持つことを意味していて、すなわち、グローバル変数は関数が呼び出される環境ではなく、定義されている環境(またはコンテキスト)から値を取得している。
上記の関数は、グローバルが7と定義されている節で定義されているので、関数がどこで使用されていてもの値は7となる。
PASCALやALGOLのような本来の命令型言語も静的束縛を持っているにもかかわらず、静的束縛はIswimの逐次的な命令的解釈の観点からはむしろ理解しにくい場合がある。
(※この例の場合、命令型言語であれば、変数をローカルな変数として「明示的に」定義することでグローバルなを隠してしまうようにすれば(シャドーイング)、グローバルなは(実質的に)静的に束縛されている状態になるわけだけど、Iswimの文を命令型言語と同じように見ると、ローカルを定義しているのではなく、定義済みのグローバルに値を再割り当てしているように見えるので、このような間違いを起こしやすい、ということ)
一方、純粋に宣言的な方法でIswimを解釈し、その定義を任意の仮引数に対して真となる方程式と考えると、静的束縛は完全に自然なものとなる。
以下の定義を考えてみる:
C = 5; B = 7; f(X, Y) = X + B * Y + C;
最初の定義はが5であり、2つ目の定義はが7であることを述べている。
そして、3つ目の定義は、とにどんな値がこようとも、の値がであることを述べている。
しかし、との値がそれぞれ5と7であることは既に分かっているので、の値はとの値が何であってもであると考えることが出来る。
Iswimの宣言的な見方は、Iswimの形式的意味にとても近いので、正しい答えを導き出す。
Iswimの解釈は、値を個々の変数に、関数を関数変数に関連付ける。
Iswimの割り当て/呼び出しモデル(=手続き的な解釈)は、(関数ではなく)「手続き本体」を関数変数に関連付けている(ように考える)ので、誤解を招く。
手続き本体は、グローバル変数の出現を含むことができるテキスト(式)であるが、実際の数学の関数はテキストではなく(それは値の集合間の対応)、「グローバル変数」を持つことは出来ない。
Iswimの命令的解釈には、関数について誤解を招く可能性がある別の微妙な問題がある。
以下のプログラムを考えてみる:
f(3, 4) where f(X, Y) = if X < 1 then 0 else f(x - 1, f(X, Y + 1) fi; end
この出力はどうなるだろうか?
形式的意味論(および宣言的解釈)であれば、答えは0でなければならない。
変数は、常に0を返す2変数の関数として定義されていて、それがこの方程式を満たす唯一の解である。
(※第2引数がなんであれ、第1引数は常に値が減っていくので、どこかでとなるため)
しかし、命令的解釈では、の呼び出しがいつまでたっても終わらず、プログラムの出力はになると指し示されてしまうだろう。
問題は、命令的解釈に従えば、の呼び出しによってが呼び出され、それはさらにを呼び出し、の呼び出し、の呼び出し、と無限に続いてしまうことにある。
実際、このプログラムをPASCALで書き直すと、ずっと深いネストされた呼び出しを生成するので、実行は永遠に終わらない。
関数呼び出しに対する命令型のアプローチの問題は、結果が生成される前に、すべての実引数の値が手続き本体で実際に必要とされることを前提としていることである。
命令型の方法は、手続き本体を実行する前に、シンプルにそれらの値(=実引数の値)をすべて計算する。
実際、結果を出すのに仮引数の値をすべて使わなくてもいい関数を定義できることは、とても有益であるーーたとえ、特定の結果を出すのに、引数のどれが必要となるかを前もって正確に言うことは出来ないとしても。
例えば、PASCALプログラマが言語にif
-then
-else
式(※C言語の3項演算子?:
と考えればいいはず)がないのに嘆いて、そのギャップを以下のように定義されたcond
関数で埋めようとしたとする:
function cond(P : bool; X, Y : integer) : integer; begin if P then cond := X else cond := Y end;
そして、階乗の関数を(ローカル変数への)割り当てなしで次のように記述したとしよう:
function fac(n : integer) : integer; begin fac := cond(n < 1, 1, n * fac(n - 1)) end
この目論見はうまくいかず、fac
の呼び出しは何も値を返さない。
なぜなら、cond
関数を呼び出す前に、n * fac(n - 1)
を評価しようとするので、n
の値によって処理が分岐せず、fac
の呼び出しが無限に続いてしまうからだ。(※ここ、原文のままだと意味が通じないと思ったので、文を書き変えた)
しかし、Iswimでは、このアイディアは機能する。
すなわち、ユーザは自身で選択関数を定義することが出来る。
以下のプログラムの出力は、形式的意味論に従えば、6となる:
fac(3) where fac(n) = cond(n<1, 1, n * fac(n-1)) where cond(p, x, y) = if p then x else y fi; end end
その理由は、cond(p, x, y)
の値は常にif p then x else y fi
の値と同じだからであるーーたとえy
の値がであったとしてもーー言い換えれば、たとえy
の計算が永遠に終わらないものであったとしても。
もちろん、命令的な観点だと、上記のプログラムが決して値を返さないことを示唆することになる。
その素朴な見方(=命令的な観点)は、単に間違っていることになる。
以上から、Iswim()の逐次的な命令的観点は不適切であり、(素朴に従うのだとしても)間違ってさえいるというのは明らかである。
しかし、解釈を修正し、完全に正しいにも関わらずに従来通りであるような実装の基礎として使用することは可能である。
そのためには、グローバルの束縛が動的であることを保証する必要が出てくる(それはそこまで難しくない)。
しかし、関数の仮引数が(事前に評価される)「値」によってではなく、(必要に応じて評価される)「名前」によってであるようにすることも保証する必要がある。
これははるかに困難であるが、不可能ではないーーALGOLはオプションで「名前」による呼び出しが出来る。
それでも、命令的観点は、たとえそれを動かせることが出来たとしても、推薦されるべきではない。
それは単にIswimの「精神」とは違っている。
ということで、前回でIswimのプログラムを手続き型のように解釈する方法を説明したけど、その解釈で出てくる問題を指摘しているのが、今回の話。
まぁ、今の時代から見れば、変数の静的束縛はラムダ式でのキャプチャと考えればいいし、引数の評価のタイミングも、引数に関数呼び出しをベタに書くんじゃなくてラムダ式を書けば評価が遅延されるよね、とかあるけど。
もちろん、引数の式をどの順番で評価するかとかはコンパイラの実装依存となっているので、式に副作用があったりすると、微妙な問題を生み出したりするというのは正しいわけだけど。
そして、評価の遅延の話が出てきてるけど、それについては次の節が「遅延評価」のようなので、また次回に。
今日はここまで!