@@ -420,9 +420,10 @@ この、普通に、数字の 1 は数字の 1 として、2 は 2 として、足し算は足し算として実行するのが、 「@value レイヤ」です。 レイヤを明示的に指定するには、レイヤ名( ... ) という構文を使います。 -なので、以下のように書いても同じ意味です。 +レイヤ指定式 と読んでいます。 +つまり、さっきのコードは以下のようにも書けます。
>> @value( 1 + 2 ) 3@@ -437,167 +438,327 @@ レイヤでのコードの意味しか知りません。@hoge レイヤでは 3 というのがどんな意味なのか、わかりません!というエラーが出ています。
-これを教えてあげるためには、@hoge レイヤの リフト関数 を定義します。 +これを教えてあげるためには、@hoge レイヤの リフト関数 というものを定義します。
>> @@hoge = fun(x){ x*2 } (function:1bdc5c0:1ba8580)
-「@ レイヤ名 = ...」文で、 -
--[Layers :: Overview] - - Polemy's runtime environment has many "layer"s. - Usual execution run in the @value layer. - - >> 1 + 2 - 3 - >> @value( 1 + 2 ) - 3 - - Here you can see that @LayerName( Expression ) executes the inner Expression in - the @LayerName layer. Other than @value, one other predefined layer exists: @macro. - - >> @macro( 1+2 ) - {pos@value:{lineno@value:3, column@value:9, filename@value:}, - is@value:app, - args@value:{car@value:{pos@value:{lineno@value:3, column@value:9, filename@value: }, - is@value:int, - data@value:1}, - cdr@value:{ - car@value:{pos@value:{lineno@value:3, column@value:11, filename@value: }, - is@value:int, - data@value:2}, - cdr@value:{}}}, - fun@value:{pos@value:{lineno@value:3, column@value:10, filename@value: }, - is@value:var, - name@value:+}} - - (Sorry, this pretty printing is not available on the actual interpreter...) - This evaluates the expression 1+2 in the @macro layer. In this layer, the meaning of - the program is its abstract syntax tree. - - You can interleave layers. - The root node of the abstract syntax tree is function "app"lication. - - >> @value(@macro( 1+2 ).is) - app - - - -[Layers :: Defining a new layer] - - To define a new layer, you should first tell how to "lift" existing values two the new layer. - Let us define the "@type" layer, where the meaning of programs is their static type. - - >> @@type = fun(x) { - >> if( _isint(x) ) { "int" } else { - >> if( _isfun(x) ) { x } else { "unknown" } } - >> } - (Note: polemy REPL may warn some exception here but please ignore) - - For simplicity, I here deal only with integers. - _isint is a primitive function of Polemy that checks the dynamic type of a value. - For function, leaving it untouched works well for almost all layers. - +@hoge レイヤでは、1 というコードの意味は 2、 +2 というコードの意味は 4、…、という、全部「2倍した意味」を持っていることにします。 +「@ レイヤ名 = ...」 という構文を使います。 +ここには、「@value レイヤでの値 x は @hoge レイヤではどういう意味になるか?」 +を計算して返す関数を登録します。 +これで、Polemy にも、@hoge レイヤの意味がわかるようになりました。 + + + >> @hoge( 3 ) + 6 +++では、1+2 を @hoge レイヤで動かしてみましょう。 +
++ >> @hoge( 1 + 2 ) + polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(466): + [+:3:7] only @value layer can call native function: + + [ :3:7] + + +まだエラーですね。これは要するに "+" の意味がわからない、と言っています。 +レイヤ指定変数定義式 で、"+" の意味を教えてあげます。 +
++ >> @hoge "+" = fun(x, y) {x} + (function:182eca0:18435e0) + >> @hoge( 3 + 4 ) + 6 +++できました。 +
++他の組み込み関数の意味も決めてみましょう。この @hoge レイヤでは、 +引き算のつもりで書いたコードが、掛け算になってしまうのだ! +
++ >> @hoge "-" = fun(x, y) {x * y} + (function:1b4c6a0:1b4fbe0) + >> @hoge( 5 - 6 ) + polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(469): + [+:3:24] only @value layer can call native function: * + [ :3:24] * + [ :4:8] - + +5、の意味は 10 で 6 の意味は 12 なので、10 - 12 と見せかけて掛け算して 120 が返るのだ! +と思いきや、エラーになってしまいました。なぜでしょう。それは、この "-" の定義、 +
+fun(x, y) {x * y}
自体が、@hoge レイヤで実行されるからです。 +掛け算はまだ定義していません。 ++ここは、「普通の」意味の掛け算を使いたいのです。 +この部分については、@value レイヤで計算して欲しい。 +そんなときは、レイヤ指定式を使います。 +
++ >> @hoge "-" = fun(x, y) {@value(@hoge(x) * @hoge(y))} + (function:1b086c0:1b4fbe0) + >> @hoge( 5 - 6 ) + 120 +++できました。掛け算は、@value レイヤの意味で実行します。 +各変数は、@hoge レイヤで計算された意味を使います、という意味になります。 +
+ + + ++関数の自動リフト + + + + + ++ + + +続きです。ちょっと関数を定義してみました。 +
++ >> def twoMinus(x,y,z) { x - y - z } + (function:1b26420:1b4fbe0) + >> twoMinus(1,2,3) + -4 +++@value レイヤで実行すると、当然、1 から 2 と 3 を引いて、-4 です。 +
++ >> @hoge( twoMinus(1,2,3) ) + 48 +++@hoge レイヤだと、2 と 4 と 6 を掛け算するので、結果は 48 です。 +
++1, 2, 3 のような値と、+ や - のような組み込み関数については、 +「@hoge レイヤでの意味」をレイヤを定義する人が決めてやる必要があります。 +でも、それさえ決めれば、あとはプログラム中で自分で定義した関数はすべて、 +Polemy 側で自動的にそのレイヤでの意味で実行できるようになります。 +
++レイヤ指定変数定義を使って、変数の意味をそのレイヤでだけ上書きして、 +違う意味を与えてやっても構いません。 +
++ >> def twoMinus(x,y,z) { x - y - z } # @value レイヤでの定義 + >> @hoge twoMinus(x,y,z) { 21 } # @hoge レイヤでの定義 + >> twoMinus(1,2,3) + -4 + >> @hoge( twoMinus(1,2,3) ) + 42 +++こんな感じで。 +
++レイヤ指定引数 + + + + + ++ + + +ここまでのサンプルでは、コードを書いた人が、レイヤ指定式で明示的にレイヤを切り替えていました。 +レイヤ指定引数 を使うと、ライブラリ関数などを書くときに、 +「この関数の第2引数は @hoge レイヤで計算して欲しい」 +といった指定ができます。 +
++ >> def f(x, y @hoge) { x + @hoge(y) } + >> f(1, 2) + 5 +++f の第2引数は、必ず @hoge レイヤで解釈されます。 +
++ >> def ff(x, y @hoge @value) { x + @hoge(y) + @value(y) } + >> ff(1, 2) + 7 +++@hoge と @value の両方のレイヤで解釈して欲しい、という欲張りな人は、 +レイヤ指定を複数並べて下さい。 +
++なにもレイヤ指定がないと、ニュートラルレイヤ指定 と呼ばれ、 +その関数の呼び出し側が解釈されていたレイヤと同じところにセットされます。 +let, var, def による変数定義も同じで、 +@hoge x = ... とレイヤを明示するとそのレイヤでの変数の意味が定義されますが、 +let x = ... とレイヤ指定しないで書くと、現在解釈中のレイヤに定義、という動作をします。 +
++ボトムと自動メモ化 + + + + + ++ + + +パターンマッチ失敗時と、"..." という式を実行したときと、再帰が無限に止まらなくなったとき、 +には、Polemy のコードは実行時エラーで終了します……@value レイヤならば。 +
++ユーザー定義レイヤでは、このような時にも実行時エラーにならず、 +「ボトム」という特別な値がリフト関数に渡されます。 +組み込みの _isbot 関数で、ボトムかどうか判定できます。 +
++「再帰が無限に止まらなくなったとき」は、 +ある引数で呼び出された関数が、return するよりも前にまた同じ引数で呼び出されたら、 +ループしていると見なすことで判定しています。 +これを判定する実装の副作用として、ユーザー定義のレイヤでは、関数は全てメモ化されています。 +つまり、ある関数が2回同じ引数同じ環境で呼び出されたら、1回目の答えをキャッシュしておいて、 +2回目は計算をせずに即座にキャッシュをひいて答えを返します。 +
++まとめ + + + + + ++ + + +まとめると、以下の機能があります。 +
++
+- @@layer = fun(x) { ... } in ... で、 + @value レイヤの値に別のレイヤでの意味を与えるリフト関数を定義
+- @layer x = ... in ... で、そのレイヤでのその変数の意味を定義
+- どちらも let/var/def 式の特殊形なので、@@layer(x) { ... } in ... などの略記も可。
+- 式の途中で @layer( ... ) と書くと、レイヤを明示的に切り替えられる
+- 関数の仮引数に fun(x @layer){ ... } とレイヤを指定すると、 + 対応する実引数はそのレイヤで解釈される。
++
++例 + + + + + +@@ -620,11 +781,8 @@ +具体的な「値」のかわりに、その「メタ情報」を取り出して、それが処理によってどう変化するか、 +といった情報を解析するのを主な用途として、この機能を作ってみました。 +プログラムでよく使われる代表的なメタ情報は、「型」です。 +サンプルとしては、sample/type.pmy をご覧下さい。以下、簡単な概略。 +
++ @@type = fun(x) { + if( _isint(x) ) then "int" + else if( _isstr(x) ) then "str" + else if( _isbot(x) ) then "runtime error" + else "type error" + } ++>> @type( 1 ) int >> @type( 2 ) int >> @type( "foo" ) - unknown - - Fine! Let's try to type 1+2. - - >> @type( 1 + 2 ) - ...\value.d(119): [+:6:8] only @value layer can call native function - - Note that the behavior of this program is - - run 1+2 in the @type layer - and NOT - - run 1+2 in @value and obtain 3 and run 3 in the @type. - The problem is, the variable "+" is defined only in the @value layer. - To carry out computation in the @type layer. We need to define it also - in the @type layer. - - To define some variable in a specific layer, use @LayerName in place of - (let|var|def)s. - - >> let x = 2 - >> @value x = 2 - >> @type x = "int" - >> @hoge x = "fuga" - - For "+", do it like this. - - >> @type "+" = fun(x,y) {@value( - >> if( @type(x)=="int" && @type(y)=="int" ) { "int" } else { "typeerror" } - >> )} - polemy.value.native!(IntValue,IntValue,IntValue).native.__anonclass24 - - It is just computing the return type from the input type. - Not here that the intended "meaning" of if-then-else is the runtime-branching, - and the meaning of "==" is the value-comparison. These are the @value layer - behavior. So we have defined the function body inside @value layer. - But when we refer the variables x and y, we need its @type layer meaning. - Hence we use @type() there. - - Now we get it. - + str + +こんな風に、値をメタ情報へ抽象化するのが、リフト関数です。 +
++型に抽象化したレイヤでの、組み込み関数の意味を考えましょう。 +"+" は、"int" と "int" を足したら "int" を返す関数です。 +それ以外なら"型エラー"を返します。そういう関数です。 +
++ var int_int_int = fun (x, y) {@value( + var tx = @type(x); + var ty = @type(y); + if tx=="runtime error" then ty + else if ty=="runtime error" then tx + else if tx=="int" && ty=="int" then "int" + else "type error" + )}; + + @type "+" = int_int_int; + @type "-" = int_int_int; + @type "<" = int_int_int; ++>> @type( 1 + 2 ) int - - Well, but do we have to define the @type layer meaning for every variables??? - No. After you defined @type "+", you'll automatically get the following: - - >> def double(x) { x + x } - (function:17e4740:1789720) - - >> @type( double(123) ) - int - - Every user-defined functions are automatically "lift"ed to the appropriate layer. - Only primitive functions like "+" requires @yourNewLayer annotation. - - - -[Layers :: neutral-layer] - - let|var|def is to define a variable in the "current" layer. - Not necessary to the @value layer. - - >> @value( let x = 1 in @value(x) ) - 1 - - >> @macro( let x = 1 in @value(x) ) - polemy.failure.RuntimeException: [+:14:29] variable x not found - - >> @macro( let x = 1 in @macro(x) ) - {pos@value:{lineno@value:15, ... - - - -[Layers :: Layered-Parameters] - - >> def foo(x @macro @value) { {fst: x, snd: @macro(x)} } - (function:1730360:1789720) - - If you annotate function parameters by @LayerNames, when you invoke the function... - - >> foo(1+2) - {snd@value: {pos@value:{lineno@value:17, column@value:5, filename@value: }, - is@value:app, arg@value:{... - /fst@value:3 - /} - - its corresponding arguments are evaluated in the layer and passed to it. - If you specify multiple layers, the argument expression is run multiple times. - If you do not specify any layer for a parameter, it works in the neutral layer. + >> @type( 1 + "foo" ) + type error + +「実行時エラーについては、それが起きなければ返すはずの型」を計算するという定義に、 +ここではしています。さらに(ちょっと手抜きで int 以外を考えていない)if の型定義を考えると、 +こんな雰囲気。 +
++ @type "if" (c, t, e) {@value( + if( @type(c)=="int" || @type(c)=="runtime error" ) then + @type( int_int_int(t(), e()) ) + else + "type error" + )} +++関数が自動リフトされるので、フィボナッチ関数の型を調べることができます。 +
++ >> def fib(x) { if x<2 then 1 else fib(x-1)+fib(x-2) }; + >> @type( fib(100000000000000) ) + int + >> def gib(x) { if x<2 then 1 else gib(x-1)+gib(x-"str") }; + >> @type( gib(100000000000000) ) + type error +++この定義で fib(100000000000000) を @value レイヤで普通に計算して、 +結果の型を見る、というのでは時間がいくらあっても足りません。 +いったん @type のメタ情報の世界に移ってから計算できるのが、レイヤ機能の肝です。 +
++正確には、この定義で @type レイヤに移ると fib("int") を無限に呼び出し続けて止まらなくなるのですが、 +そこは、自動メモ化による再帰検出でボトム値を返す機能によって、うまく止まっています。 +
++それでも上手く型計算ができない(あるいはすごく遅くなる)ような複雑な関数があるかもしれません。 +仕方がないので、型情報をアノテーションとしてつけてあげることも可能です。 +
++ @type f = int_int_int; + def f(x,y) { ...とても型を計算できないくらい複雑な定義... };++これが、レイヤ指定変数定義の典型的な使い道です。 +
@value
は、「普通に」普通のセマンティクスでプログラムを実行するレイヤでした。@macro
は、実は、@value
よりも前に実行されるレイヤで、 「プログラムを実行するとその構文木を返す」というセマンティクスで動きます。 -- (ここに例) -動きとしてはこうです。
@@ -979,10 +1137,10 @@
- @@ -997,9 +1155,9 @@
_isint (a) a が整数なら 1、でなければ 0 _isstr (a) a が文字列なら 1、でなければ 0 - _isfun (a) a が関数なら 1、でなければ 0 - _istable (a) a がテーブルなら 1、でなければ 0 + _isundefined (a) a が未定義値なら 1、でなければ 0 + _istbl (a) a がテーブルなら 1、でなければ 0 _isbot (a) a が未定義値なら 1、でなければ 0 Page was generated with - on Sat Nov 27 22:01:28 2010 + on Sun Nov 28 00:45:59 2010