@@ -353,163 +353,259 @@
[
-まだエラーですね。これは 実は、リフト関数は
+まだエラーですね。これは要するに "+" の意味がわからない、と言っています。
+$(RED $(B レイヤ指定変数定義式)) で、"+" の意味を教えてあげます。
+できました。
+
+他の組み込み関数の意味も決めてみましょう。この @hoge レイヤでは、
+引き算のつもりで書いたコードが、掛け算になってしまうのだ!
+
+5、の意味は 10 で 6 の意味は 12 なので、10 - 12 と見せかけて掛け算して 120 が返るのだ!
+と思いきや、エラーになってしまいました。なぜでしょう。それは、この "-" の定義、
+
+ここは、「普通の」意味の掛け算を使いたいのです。
+この部分については、@value レイヤで計算して欲しい。
+そんなときは、レイヤ指定式を使います。
+
+できました。掛け算は、@value レイヤの意味で実行します。
+各変数は、@hoge レイヤで計算された意味を使います、という意味になります。
+
+続きです。ちょっと関数を定義してみました。
+
+@value レイヤで実行すると、当然、1 から 2 と 3 を引いて、-4 です。
+
+@hoge レイヤだと、2 と 4 と 6 を掛け算するので、結果は 48 です。
+
+1, 2, 3 のような値と、+ や - のような組み込み関数については、
+「@hoge レイヤでの意味」をレイヤを定義する人が決めてやる必要があります。
+でも、それさえ決めれば、あとはプログラム中で自分で定義した関数はすべて、
+Polemy 側で自動的にそのレイヤでの意味で実行できるようになります。
+
+レイヤ指定変数定義を使って、変数の意味をそのレイヤでだけ上書きして、
+違う意味を与えてやっても構いません。
+
+こんな感じで。
+
+ここまでのサンプルでは、コードを書いた人が、レイヤ指定式で明示的にレイヤを切り替えていました。
+$(RED $(B レイヤ指定引数)) を使うと、ライブラリ関数などを書くときに、
+「この関数の第2引数は @hoge レイヤで計算して欲しい」
+といった指定ができます。
+
+f の第2引数は、必ず @hoge レイヤで解釈されます。
+
+@hoge と @value の両方のレイヤで解釈して欲しい、という欲張りな人は、
+レイヤ指定を複数並べて下さい。
+
+なにもレイヤ指定がないと、$(RED $(B ニュートラルレイヤ指定)) と呼ばれ、
+その関数の呼び出し側が解釈されていたレイヤと同じところにセットされます。
+let, var, def による変数定義も同じで、
+@hoge x = ... とレイヤを明示するとそのレイヤでの変数の意味が定義されますが、
+let x = ... とレイヤ指定しないで書くと、現在解釈中のレイヤに定義、という動作をします。
+
+パターンマッチ失敗時と、"..." という式を実行したときと、再帰が無限に止まらなくなったとき、
+には、Polemy のコードは実行時エラーで終了します……@value レイヤならば。
+
+ユーザー定義レイヤでは、このような時にも実行時エラーにならず、
+「$(RED $(B ボトム))」という特別な値がリフト関数に渡されます。
+組み込みの _isbot 関数で、ボトムかどうか判定できます。
+
+「再帰が無限に止まらなくなったとき」は、
+ある引数で呼び出された関数が、return するよりも前にまた同じ引数で呼び出されたら、
+ループしていると見なすことで判定しています。
+これを判定する実装の副作用として、ユーザー定義のレイヤでは、関数は全てメモ化されています。
+つまり、ある関数が2回同じ引数同じ環境で呼び出されたら、1回目の答えをキャッシュしておいて、
+2回目は計算をせずに即座にキャッシュをひいて答えを返します。
+
+まとめると、以下の機能があります。
+
+
+具体的な「値」のかわりに、その「メタ情報」を取り出して、それが処理によってどう変化するか、
+といった情報を解析するのを主な用途として、この機能を作ってみました。
+プログラムでよく使われる代表的なメタ情報は、「型」です。
+サンプルとしては、sample/type.pmy をご覧下さい。以下、簡単な概略。
+
+こんな風に、値をメタ情報へ抽象化するのが、リフト関数です。
+
+型に抽象化したレイヤでの、組み込み関数の意味を考えましょう。
+"+" は、"int" と "int" を足したら "int" を返す関数です。
+それ以外なら"型エラー"を返します。そういう関数です。
+
+「実行時エラーについては、それが起きなければ返すはずの型」を計算するという定義に、
+ここではしています。さらに(ちょっと手抜きで int 以外を考えていない)if の型定義を考えると、
+こんな雰囲気。
+
+関数が自動リフトされるので、フィボナッチ関数の型を調べることができます。
+
+この定義で fib(100000000000000) を @value レイヤで普通に計算して、
+結果の型を見る、というのでは時間がいくらあっても足りません。
+いったん @type のメタ情報の世界に移ってから計算できるのが、レイヤ機能の肝です。
+
+正確には、この定義で @type レイヤに移ると fib("int") を無限に呼び出し続けて止まらなくなるのですが、
+そこは、自動メモ化による再帰検出でボトム値を返す機能によって、うまく止まっています。
+
+それでも上手く型計算ができない(あるいはすごく遅くなる)ような複雑な関数があるかもしれません。
+仕方がないので、型情報をアノテーションとしてつけてあげることも可能です。
+
+これが、レイヤ指定変数定義の典型的な使い道です。
+
>> @hoge "+" = fun(x, y) {x}
-
-
-[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:
+
+ >> @hoge "-" = fun(x, y) {x * y}
+ (function:1b4c6a0:1b4fbe0)
+ >> @hoge( 5 - 6 )
+ polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(469):
+ [
+fun(x, y) {x * y}
自体が、@hoge レイヤで実行されるからです。
+掛け算はまだ定義していません。
+
+ >> @hoge "-" = fun(x, y) {$(B @value(@hoge(x) * @hoge(y)))}
+ (function:1b086c0:1b4fbe0)
+ >> @hoge( 5 - 6 )
+ 120
+
+
+ >> def twoMinus(x,y,z) { x - y - z }
+ (function:1b26420:1b4fbe0)
+ >> twoMinus(1,2,3)
+ -4
+
+
+ >> @hoge( twoMinus(1,2,3) )
+ 48
+
+
+ >> def twoMinus(x,y,z) { x - y - z } $(D_COMMENT # @value レイヤでの定義)
+ >> @hoge twoMinus(x,y,z) { 21 } $(D_COMMENT # @hoge レイヤでの定義)
+ >> twoMinus(1,2,3)
+ -4
+ >> @hoge( twoMinus(1,2,3) )
+ 42
+
+
+ >> def f(x, y $(B @hoge)) { x + @hoge(y) }
+ >> f(1, 2)
+ 5
+
+
+ >> def ff(x, y $(B @hoge @value)) { x + @hoge(y) + @value(y) }
+ >> ff(1, 2)
+ 7
+
+
+
+
+ @@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 )
+ >> @type( 2 )
int
- >> @type( "foo" )
- unknown
-
- Fine! Let's try to type 1+2.
-
- >> @type( 1 + 2 )
- ...\value.d(119): [
+
+ 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"
+ )};
- 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.
-
- >> @type( 1 + 2 )
+ @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: [
+
+ @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
+
+
+ @type f = int_int_int;
+ def f(x,y) { ...とても型を計算できないくらい複雑な定義... };
+@value
は、「普通に」普通のセマンティクスでプログラムを実行するレイヤでした。
@macro
は、実は、@value
よりも前に実行されるレイヤで、
「プログラムを実行するとその構文木を返す」というセマンティクスで動きます。
- (ここに例) -
動きとしてはこうです。