セマンティクスの違い
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2021/11/26 23:59 UTC 版)
言語ごとにスコープのセマンティクスが異なるように、クロージャの定義も異なっている。汎用的な定義では、クロージャが捕捉する「環境」とは、あるスコープのすべての変数の束縛の集合である。しかし、この変数の束縛というものの意味も言語ごとに異なっている。命令型言語では、変数は値を格納するためのメモリ中の位置と束縛される。この束縛は変化せず、束縛された位置にある値が変化する。クロージャは束縛を捕捉しているので、そのような言語での変数への操作は、それがクロージャからであってもなくとも、同一のメモリ領域に対して実行される。例として、ECMAScriptを取り上げると var f, g;function foo(){ var x = 0; f = function() { x += 1; return x; }; g = function() { x -= 1; return x; }; x = 1; console.log(f()); // "2"}foo();console.log(g()); // "1"console.log(f()); // "2" 関数 foo と2つのクロージャがローカル変数 x に束縛された同一のメモリ領域を使用していることに注意。 一方、多くの関数型言語、例えばML、は変数を直接、値に束縛する。この場合、一度束縛された変数の値を変える方法はないので、クロージャ間で状態を共有する必要はない。単に同じ値を使うだけである。 さらに、Haskellなど、遅延評価を行う関数型言語では、変数は将来の計算結果に束縛される。例を挙げる。 foo x y = let r = x / y in (\z -> z + r)f = foo 1 0main = do putStr (show (f 123)) r は計算 (x / y) に束縛されており、この場合は0による除算である。しかしながら、クロージャが参照しているのはその値ではなく計算であるので、エラーはクロージャが実行され、実際にその束縛を使おうと試みたときに現れる。 さらなる違いは静的スコープである制御構文、C言語風の言語における return・break・continue などにおいて現れる。ECMAScriptなどの言語では、これらはクロージャ毎に束縛され、構文上の束縛を隠蔽する。つまり、クロージャ内からの return はクロージャを呼び出したコードに制御を渡す。しかしSmalltalkでは、このような動作はトップレベルでしか起こらず、クロージャに捕捉される。例を示して、この違いを明らかにする。 "Smalltalk"foo | xs | xs := #(1 2 3 4). xs do: [:x | ^x]. ^0bar Transcript show: (self foo) "prints 1" // ECMAScriptfunction foo() { var xs = new Array(1, 2, 3, 4); xs.forEach(function(x) { return x; }); return 0;}print(foo()); // prints 0 Smalltalkにおける ^ はECMAScriptにおける return にあたるものだと頭に入れれば、一目見た限りではどちらのコードも同じことをするように見える。違いは、ECMAScriptの例では return はクロージャを抜けるが関数 foo は抜けず、Smalltalkの例では ^ はクロージャだけではなくメソッド foo をも抜ける、という点である。後者の特徴はより高い表現力をもたらす。Smalltalkの do: は通常のメソッドであり、自然に制御構文が定義できている。一方、ECMAScriptでは return の意味が変わってしまうので、同じ目的には foreach という新しい構文を導入しなければならない。 しかし、スコープを越えて生存する継続には問題もある。 foo ^[ x: | ^x ]bar | f | f := self foo. f value: 123 "error!" 上の例でメソッド foo が返すブロックが実行されたとき、foo から値を返そうとする。しかし、foo の呼び出しは既に完了しているので、この操作はエラーとなる。
※この「セマンティクスの違い」の解説は、「クロージャ」の解説の一部です。
「セマンティクスの違い」を含む「クロージャ」の記事については、「クロージャ」の概要を参照ください。
- セマンティクスの違いのページへのリンク