Index: doc/_common.html ================================================================== --- doc/_common.html +++ doc/_common.html @@ -24,11 +24,11 @@ Page was generated with - on Sat Nov 27 01:17:56 2010 + on Sat Nov 27 23:29:16 2010 +
+関数の自動リフト + + +
+ + +

+続きです。ちょっと関数を定義してみました。 +

+
+    >> 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回目は計算をせずに即座にキャッシュをひいて答えを返します。 +

+
+ + +
+まとめ + + +
+ + +

+まとめると、以下の機能があります。 +

+ +

+

+
+ + +
+ + + +
+ + +

+具体的な「値」のかわりに、その「メタ情報」を取り出して、それが処理によってどう変化するか、 +といった情報を解析するのを主な用途として、この機能を作ってみました。 +プログラムでよく使われる代表的なメタ情報は、「型」です。 +サンプルとしては、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) { ...とても型を計算できないくらい複雑な定義... };
 
+

+これが、レイヤ指定変数定義の典型的な使い道です。 +

@@ -619,13 +780,10 @@ (内部的にはもういくつかありますが、ユーザから直接は使えません。) @value は、「普通に」普通のセマンティクスでプログラムを実行するレイヤでした。 @macro は、実は、@value よりも前に実行されるレイヤで、 「プログラムを実行するとその構文木を返す」というセマンティクスで動きます。

-
-    (ここに例)
-

動きとしてはこうです。

  1. 関数呼び出し時(とトップレベル環境の実行開始時)に、 @@ -978,12 +1136,12 @@
    - - + +
    _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
    @@ -996,11 +1154,11 @@ Page was generated with - on Sat Nov 27 22:01:28 2010 + on Sun Nov 28 00:45:59 2010