<html><head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta content="text/javascript" http-equiv="content-script-type">
<title>Polemy Reference Manual</title>
<link rel="stylesheet" type="text/css" href="candydoc/style.css">
<!--[if lt IE 7]><link rel="stylesheet" type="text/css" href="candydoc/ie56hack.css"><![endif]-->
<script language="JavaScript" src="candydoc/util.js" type="text/javascript"></script>
<script language="JavaScript" src="candydoc/tree.js" type="text/javascript"></script>
<script language="JavaScript" src="candydoc/explorer.js" type="text/javascript"></script>
</head><body>
<div id="tabarea"></div><div id="explorerclient"></div>
<div id="content"><script>explorer.initialize("Polemy Reference Manual");</script>
<table class="content">
<tr><td id="docbody"><h1>Polemy Reference Manual</h1><!-- Generated by Ddoc from index.dd -->
<b>Authors:</b><br>
k.inaba<br><br>
<b>License:</b><br>
NYSL 0.9982 (http://www.kmonos.net/nysl/)<br><br>
<p>
左のサイドバーの "Package" タブをクリックすると実装のソースのドキュメントが読めます。
</p>
<p>
このファイルは、言語仕様などの、やや辞書的な説明です。<br />
もっとざっくりとした、言語デザインの方向性の魂的なものについては、
「メタプログラミングの会」の発表スライドをご覧下さい。
</p>
<p>
あと、 やたらとマクロの章が長くなっていますが、 この部分は、
レイヤ機能を入れたら自動的にすごく自然にマクロが入るなーと思って、
おまけで実装してみた程度のものです。
あんまり重要ではないので、適当にスルーして下さいませ。
単に、適当に入れたら適当で微妙な部分が多く残ってしまったので注意書きが増えているだけで…。
</p>
<p>
言い訳ついでにもう一つ言い訳ですが、この言語は、少なくとも今のところ、
実用に使うことを考えた設計にはなっていません。どちらかというと、
Brainfuck や Unlambda や Whitespace の仲間と思ってお使い下さい。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">Syntax</span>
<script>explorer.outline.addDecl('Syntax');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
文法について。
字句解析がわりと適当なので、
変数宣言の変数名のところに、数字を変数名として使えて参照できない変数が作れたり、
予約語は予約語として解釈され得ないところでは普通に変数名として使えちゃったりして、
偶にとんでもない見かけのソースが構文解析通りますが、気にしないで適当に使って下さい。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">文字コード</span>
<script>explorer.outline.addDecl('文字コード');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
UTF-8 のみ対応です。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">コメント</span>
<script>explorer.outline.addDecl('コメント');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
行コメントは <tt>#</tt> から改行までです。
</p>
<p>
ブロックコメントはありません。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">BNF</span>
<script>explorer.outline.addDecl('BNF');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><pre>
ID ::= 適当に識別子っぽい文字列
LAYER ::= "@" ID
E ::=
<font color=green># 変数宣言</font>
| DECL "=" E (";"|"in") E
| DECL "(" PARAMS ")" "{" E "}" (";"|"in") E
| DECL "=" E
| DECL "(" PARAMS ")" "{" E "}"
where DECL ::= ("var"|"let"|"def"|LAYER) ID | "@" LAYER
<font color=green># リテラル</font>
| INTEGER <font color=green># 非負整数</font>
| STRING <font color=green># "" でくくった文字列。\" と \\ は使える</font>
| "{" ENTRYS "}" <font color=green># テーブル</font>
| "fun" "(" PARAMS ")" "{" E "}" <font color=green># 無名関数</font>
| "λ" "(" PARAMS ")" "{" E "}" <font color=green># 無名関数</font>
<font color=green># 関数呼び出し</font>
| E "(" ARGS")"
where ARGS ::= E "," ... "," E
PARAMS ::= (ID|LAYER)+ "," ... "," (ID|LAYER)+
ENTRYS ::= ID ":" E "," ... "," ID ":" E
<font color=green># 演算子など</font>
| "(" E ")" <font color=green># ただの括弧</font>
| "..." <font color=green># これを実行するとdie</font>
| E BINOP E <font color=green># 二項演算子いろいろ</font>
| E "." ID <font color=green># テーブルのフィールドアクセス</font>
| E ".?" ID <font color=green># テーブルにフィールドがあるか否か</font>
| E "{" ENTRYS "}" <font color=green># テーブル拡張</font>
| "if" E ("then"|":"|"then" ":") E
| "if" E ("then"|":"|"then" ":") E "else" ":"? E
<font color=green># パターンマッチ</font>
| "case" E ("when" PATTERN ":" E )*
where PATTERN ::= 式がだいたいなんでも書ける気がする
<font color=green># レイヤ指定実行</font>
| LAYER "(" E ")"
</pre>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">糖衣構文</span>
<script>explorer.outline.addDecl('糖衣構文');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
演算子というものはありません。内部的には全て関数呼び出し構文に書き換えられています。<tt>if</tt> もです。
<br/>
パターンマッチも全部 <tt>if</tt> と <tt>==</tt> と <tt>&&</tt> と
<tt>.</tt> と <tt>.?</tt> を使った関数呼び出し式に書き換えられていますが、
規則の詳細を説明するのが面倒なので適当に想像して下さい。
他の書き換えはこんな感じです。
</p>
<pre>
if E then E ⇒ if( E, fun(){E}, fun(){} )
if E then E else E ⇒ if( E, fun(){E}, fun(){E} )
E BINOP E ⇒ BINOP(E, E)
{ ENTRIES } ⇒ {}{ ENTRIES }
{} ⇒ {}()
E {ID:E, ...} ⇒ .=(E, ID, E) { ... }
</pre>
<p>
変数宣言に色々ありますが、<tt>let</tt> と <tt>var</tt> と <tt>def</tt> は同じ扱いで、
<tt>in</tt> と <tt>;</tt> は同じ扱いです。つまり
</p>
<pre>
let x = E in E
var x = E in E
def x = E in E
let x = E ; E
var x = E ; E
def x = E ; E
</pre>
<p>
以上のどれも同じ意味なので、なんとなく関数型っぽく書きたい気分の日は <tt>let in</tt> を、
手続き型っぽく書きたい気分の日は <tt>var ;</tt> を使うとよいと思います。
<tt>if then else</tt> も微妙にコロンがあったりなかったりバリエーションがありますが好みで使います。
</p>
<p>
関数を宣言するときは、<tt>fun</tt> や <tt>λ</tt> を省略できます。
以下の書き換えが行われます。
</p>
<pre>
def f( ARGS ) { E }; E ⇒ def f = fun(ARGS){E}; E
</pre>
<p>
他に、もっと手続き型っぽくための書き換え色々
</p>
<pre>
fun () { E; E; E } ⇒ fun () { let _ = E in let _ = E in E }
fun () { var x = 100 } ⇒ fun () { var x = 100; x }
fun () { var x = 100; } ⇒ fun () { var x = 100; x }
fun () { } ⇒ fun () { "(empty function body)" }
</pre>
<p>
中身が空の関数に何を返させるかは適当です。今はとりあえず適当に文字列返してます。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">変数のスコープ規則</span>
<script>explorer.outline.addDecl('変数のスコープ規則');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
基本的には、let によって常識的な感じに変数のスコープがネストします。
</p>
<pre>
let x=21 in let x=x+x in x <font color=green># 42</font>
</pre>
<p>
一方で、"let rec" のような特別な構文はありませんが、
</p>
<pre>
let f = fun(x) { if x==0 then 1 else x*f(x-1) } in f(10) <font color=green># 3628800</font>
</pre>
<p>
再帰的な関数定義なども、おそらく意図されたとおりに動きます。
内部の詳細は、諸般の事情により、
マジカルで破壊的なスコープ規則になっているのですが、
同名の変数を激しく重ねて使ったりしなければ、
だいたい自然な動きをすると思います、たぶん、はい。
</p>
<p>
ひとつだけ不可思議な動きをするのは、以下のケースです。
</p>
<pre>
let x = 1 in
let f = fun() {x} in
let x = 2 in
f() <font color=green># 2!!</font>
</pre>
<p>
let-in を縦にチェインしたときだけ、同名変数を破壊的に上書きします
(再帰関数の定義が"うまく"いっているのはこの上書きのためです)。
なんでこんなことになっているかというと、
後で説明する「レイヤ」を使ったときに
<tt>let foo = ... in @lay foo = ... in ...</tt>
で他レイヤに重ね書きするため、のつもりです。詳しくは後で。
</p>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">Basic Features</span>
<script>explorer.outline.addDecl('Basic Features');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
特に特徴的でもない部分を簡単にまとめ。
</p>
<ul>
<li>静的型システムはありません。</li>
<li>"ほぼ" 純粋関数型言語です。変数やテーブルのフィールドの破壊的な書き換えはできません。<br/>
ただし、組み込み関数(<tt>print</tt>)と、変数のスコープ規則のマジカルな片隅に副作用があります。</li>
</ul>
<p>
静的型システムがないのは意図的ですが、破壊的代入がないのは、単に実装がめんどかっただけなので、
今後何か増えるかもしれません。増えないかもしれません。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">データ型</span>
<script>explorer.outline.addDecl('データ型');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
以下のデータ型があります。
</p>
<ul>
<li>整数: <tt>0</tt>, <tt>123</tt>, <tt>456666666666666666666666666666666666666789</tt>, ...</li>
<li>文字列: <tt>"hello, world!"</tt>, ...</li>
<li>関数: <tt>fun(x){x+1}</tt></li>
<li>テーブル: <tt>{car: 1, cdr: {car: 2, cdr: {}}}</tt></li>
<li>ボトム: (特殊なケースで作られます。「レイヤ」の説明参照のこと。)</li>
</ul>
<p>
関数はいわゆる「クロージャ」です。静的スコープで外側の環境にアクセスできます。
テーブルはいわゆるプロトタイプチェーンを持っていて、
自分にないフィールドの場合は親に問い合わせが行く感じになっていますが、
フィールドの書き換えがないので、これは特に意味ないかもしれない…。
</p>
<p>
また、リストを扱うために、いわゆる「cons リスト」を使います。
空リストを <tt>{}</tt>、1個以上要素があるものを <tt>{car: 先頭要素, cdr: 二番目以降のリスト}</tt>
という形で。この形でリストを扱わなければならないという決まりはありませんが、
この形は特別扱いされて <tt>print</tt> で綺麗に出力されたりします。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">パターンマッチ</span>
<script>explorer.outline.addDecl('パターンマッチ');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
適当に実装されたパターンマッチがあります。
リストの 2n 番目と 2n+1 番目を足して長さを半分にする関数:
</p>
<pre>
def adjSum(lst)
{
case lst
when {car:x, cdr:{car: y, cdr:z}}: {car: x+y, cdr: adjSum(z)}
when {car:x, cdr:{}}: lst
when {}: {}
}
</pre>
<p>
動かすときには、処理系がそれっぽい if-then-else に展開しています。
<tt>when</tt> を上から試していって、最初にマッチしたところを実行します。
どれにもマッチしないとエラーでプログラム終了します。
</p>
<pre>
PAT ::= "_" <font color=green># ワイルドカード</font>
| ID <font color=green># 変数パターン</font>
| "{" ID ":" PAT "," ... "," ID : PAT "}" <font color=green># テーブルパターン</font>
| E <font color=green># 値パターン</font>
</pre>
<p>
変数パターンは常にマッチして、値をその変数に束縛します。
ワイルドカードも常にマッチしますが、変数束縛しません。
値パターンは、任意の式が書けます。その式を評価した結果と <tt>==</tt> ならマッチします。
外で束縛された変数を値パターンとして配置、は直接はできないので
</p>
<pre>
var x = 123;
case foo
when {val: x+0}: ... <font color=green># これは {val:123} と同じ</font>
when {val: x}: ... <font color=green># これは任意の foo.?val なら常にマッチ</font>
</pre>
<p>
適当にちょっと複雑な式にしてやるとよいかも(裏技)。
</p>
<p>
テーブルパターンは、書かれたキーが全てあればマッチします。
<tt>{a: _}</tt> は、<tt>.a</tt> を持ってさえいればマッチするので、
<tt>{a: 123, b: 456}</tt> なんかにもマッチします。
なので、リストに対するパターンを書くときには、car/cdr の場合を先に書かないと
<tt>when {}</tt> を上に書くと全部マッチしちゃいます。注意。
</p>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">Layers</span>
<script>explorer.outline.addDecl('Layers');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
この言語の唯一の特徴的な部分は、「レイヤ」機能です。
</p>
<p>
ひとつのコードに複数の「意味」を持たせるのが、レイヤ機能の目的です。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">概要</span>
<script>explorer.outline.addDecl('概要');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
普通に Polemy のコードを動かすと、そのコードは「<tt>@value</tt> レイヤ」で動作します。
インタプリタで実験。
</p>
<pre>
$ bin/polemy
Welcome to Polemy 0.1.0
>> 1 + 2
3
</pre>
この、普通に、数字の 1 は数字の 1 として、2 は 2 として、足し算は足し算として実行するのが、
「<tt>@value</tt> レイヤ」です。
レイヤを明示的に指定するには、<tt>レイヤ名( ... )</tt> という構文を使います。
<font color=red><b>レイヤ指定式</b></font> と読んでいます。
つまり、さっきのコードは以下のようにも書けます。
<pre>
>> @value( 1 + 2 )
3
</pre>
他のレイヤで動かしてみましょう。適当に。「<tt>@hoge</tt> レイヤ」で。
<pre>
>> @hoge( 3 )
polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(138):
[<REPL>:4:8] lift function for @hoge is not registered
</pre>
<p>
エラーになりました。Polemy のインタプリタは、起動時には、<tt>@value</tt>
レイヤでのコードの意味しか知りません。<tt>@hoge</tt> レイヤでは <tt>3</tt>
というのがどんな意味なのか、わかりません!というエラーが出ています。
</p>
<p>
これを教えてあげるためには、<tt>@hoge</tt> レイヤの <font color=red><b>リフト関数</b></font> というものを定義します。
</p>
<pre>
>> @@hoge = fun(x){ x*2 }
(function:1bdc5c0:1ba8580)
</pre>
<p>
<tt>@hoge</tt> レイヤでは、<tt>1</tt> というコードの意味は <tt>2</tt>、
<tt>2</tt> というコードの意味は <tt>4</tt>、…、という、全部「2倍した意味」を持っていることにします。
「<tt>@ レイヤ名 = ...</tt>」 という構文を使います。
ここには、「<tt>@value</tt> レイヤでの値 <tt>x</tt> は <tt>@hoge</tt> レイヤではどういう意味になるか?」
を計算して返す関数を登録します。
これで、Polemy にも、<tt>@hoge</tt> レイヤの意味がわかるようになりました。
</p>
<pre>
>> @hoge( 3 )
6
</pre>
<p>
では、1+2 を <tt>@hoge</tt> レイヤで動かしてみましょう。
</p>
<pre>
>> @hoge( 1 + 2 )
polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(466):
[<REPL>:3:7] only @value layer can call native function: +
[<REPL>:3:7] +
</pre>
<p>
まだエラーですね。これは要するに "+" の意味がわからない、と言っています。
<font color=red><b>レイヤ指定変数定義式</b></font> で、"+" の意味を教えてあげます。
</p>
<pre>
>> @hoge "+" = fun(x, y) {x}
(function:182eca0:18435e0)
>> @hoge( 3 + 4 )
6
</pre>
<p>
できました。
</p>
<p>
他の組み込み関数の意味も決めてみましょう。この <tt>@hoge</tt> レイヤでは、
引き算のつもりで書いたコードが、掛け算になってしまうのだ!
</p>
<pre>
>> @hoge "-" = fun(x, y) {x * y}
(function:1b4c6a0:1b4fbe0)
>> @hoge( 5 - 6 )
polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(469):
[<REPL>:3:24] only @value layer can call native function: *
[<REPL>:3:24] *
[<REPL>:4:8] -
</pre>
<p>
5、の意味は 10 で 6 の意味は 12 なので、10 - 12 と見せかけて掛け算して 120 が返るのだ!
と思いきや、エラーになってしまいました。なぜでしょう。それは、この "-" の定義、
<code>fun(x, y) {x * y}</code> 自体が、<tt>@hoge</tt> レイヤで実行されるからです。
掛け算はまだ定義していません。
</p>
<p>
ここは、「普通の」意味の掛け算を使いたいのです。
この部分については、<tt>@value</tt> レイヤで計算して欲しい。
そんなときは、レイヤ指定式を使います。
</p>
<pre>
>> @hoge "-" = fun(x, y) {<b>@value(@hoge(x) * @hoge(y))</b>}
(function:1b086c0:1b4fbe0)
>> @hoge( 5 - 6 )
120
</pre>
<p>
できました。掛け算は、<tt>@value</tt> レイヤの意味で実行します。
各変数は、<tt>@hoge</tt> レイヤで計算された意味を使います、という意味になります。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">関数の自動リフト</span>
<script>explorer.outline.addDecl('関数の自動リフト');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
続きです。ちょっと関数を定義してみました。
</p>
<pre>
>> def twoMinus(x,y,z) { x - y - z }
(function:1b26420:1b4fbe0)
>> twoMinus(1,2,3)
-4
</pre>
<p>
<tt>@value</tt> レイヤで実行すると、当然、1 から 2 と 3 を引いて、-4 です。
</p>
<pre>
>> @hoge( twoMinus(1,2,3) )
48
</pre>
<p>
<tt>@hoge</tt> レイヤだと、2 と 4 と 6 を掛け算するので、結果は 48 です。
</p>
<p>
1, 2, 3 のような値と、+ や - のような組み込み関数については、
「<tt>@hoge</tt> レイヤでの意味」をレイヤを定義する人が決めてやる必要があります。
でも、それさえ決めれば、あとはプログラム中で自分で定義した関数はすべて、
Polemy 側で自動的にそのレイヤでの意味で実行できるようになります。
</p>
<p>
レイヤ指定変数定義を使って、変数の意味をそのレイヤでだけ上書きして、
違う意味を与えてやっても構いません。
</p>
<pre>
>> def twoMinus(x,y,z) { x - y - z } <font color=green># @value レイヤでの定義</font>
>> @hoge twoMinus(x,y,z) { 21 } <font color=green># @hoge レイヤでの定義</font>
>> twoMinus(1,2,3)
-4
>> @hoge( twoMinus(1,2,3) )
42
</pre>
<p>
こんな感じで。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">レイヤ指定引数</span>
<script>explorer.outline.addDecl('レイヤ指定引数');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
ここまでのサンプルでは、コードを書いた人が、レイヤ指定式で明示的にレイヤを切り替えていました。
<font color=red><b>レイヤ指定引数</b></font> を使うと、ライブラリ関数などを書くときに、
「この関数の第2引数は <tt>@hoge</tt> レイヤで計算して欲しい」
といった指定ができます。
</p>
<pre>
>> def f(x, y <b>@hoge</b>) { x + @hoge(y) }
>> f(1, 2)
5
</pre>
<p>
f の第2引数は、必ず <tt>@hoge</tt> レイヤで解釈されます。
</p>
<pre>
>> def ff(x, y <b>@hoge @value</b>) { x + @hoge(y) + @value(y) }
>> ff(1, 2)
7
</pre>
<p>
<tt>@hoge</tt> と <tt>@value</tt> の両方のレイヤで解釈して欲しい、という欲張りな人は、
レイヤ指定を複数並べて下さい。
</p>
<p>
なにもレイヤ指定がないと、<font color=red><b>ニュートラルレイヤ指定</b></font> と呼ばれ、
その関数の呼び出し側が解釈されていたレイヤと同じところにセットされます。
<tt>let</tt>, <tt>var</tt>, <tt>def</tt> による変数定義も同じで、
<tt>@hoge x = ...</tt> とレイヤを明示するとそのレイヤでの変数の意味が定義されますが、
<tt>let x = ...</tt> とレイヤ指定しないで書くと、現在解釈中のレイヤに定義、という動作をします。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">ボトムと自動メモ化</span>
<script>explorer.outline.addDecl('ボトムと自動メモ化');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
パターンマッチ失敗時と、"..." という式を実行したときと、再帰が無限に止まらなくなったとき、
には、Polemy のコードは実行時エラーで終了します……<tt>@value</tt> レイヤならば。
</p>
<p>
ユーザー定義レイヤでは、このような時にも実行時エラーにならず、
「<font color=red><b>ボトム</b></font>」という特別な値がリフト関数に渡されます。
組み込みの <tt>_isbot</tt> 関数で、ボトムかどうか判定できます。
</p>
<p>
「再帰が無限に止まらなくなったとき」は、
ある引数で呼び出された関数が、return するよりも前にまた同じ引数で呼び出されたら、
ループしていると見なすことで判定しています。
これを判定する実装の副作用として、ユーザー定義のレイヤでは、関数は全てメモ化されています。
つまり、ある関数が2回同じ引数同じ環境で呼び出されたら、1回目の答えをキャッシュしておいて、
2回目は計算をせずに即座にキャッシュをひいて答えを返します。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">まとめ</span>
<script>explorer.outline.addDecl('まとめ');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
まとめると、以下の機能があります。
</p>
<ul>
<li><tt>@@layer = fun(x) { ... } in ...</tt> で、
<tt>@value</tt> レイヤの値に別のレイヤでの意味を与えるリフト関数を定義</li>
<li><tt>@layer x = ... in ...</tt> で、そのレイヤでのその変数の意味を定義</li>
<li>どちらも let/var/def 式の特殊形なので、<tt>@@layer(x) { ... } in ...</tt> などの略記も可。</li>
<li>式の途中で @layer( ... ) と書くと、レイヤを明示的に切り替えられる</li>
<li>関数の仮引数に fun(x @layer){ ... } とレイヤを指定すると、
対応する実引数はそのレイヤで解釈される。</li>
</ul>
<p>
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">例</span>
<script>explorer.outline.addDecl('例');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
具体的な「値」のかわりに、その「メタ情報」を取り出して、それが処理によってどう変化するか、
といった情報を解析するのを主な用途として、この機能を作ってみました。
プログラムでよく使われる代表的なメタ情報は、「型」です。
サンプルとしては、sample/type.pmy をご覧下さい。以下、簡単な概略。
</p>
<pre>
@@type = fun(x) {
if( _isint(x) ) then "int"
else if( _isstr(x) ) then "str"
else if( _isbot(x) ) then "runtime error"
else "type error"
};
</pre>
<pre>
>> @type( 1 )
int
>> @type( 2 )
int
>> @type( "foo" )
str
</pre>
<p>
こんな風に、値をメタ情報へ抽象化するのが、リフト関数です。
</p>
<p>
型に抽象化したレイヤでの、組み込み関数の意味を考えましょう。
"+" は、"int" と "int" を足したら "int" を返す関数です。
それ以外なら"型エラー"を返します。そういう関数です。
</p>
<pre>
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;
</pre>
<pre>
>> @type( 1 + 2 )
int
>> @type( 1 + "foo" )
type error
</pre>
<p>
「実行時エラーについては、それが起きなければ返すはずの型」を計算するという定義に、
ここではしています。さらに(ちょっと手抜きで int 以外を考えていない)if の型定義を考えると、
こんな雰囲気。
</p>
<pre>
@type "if" (c, t, e) {@value(
if( @type(c)=="int" || @type(c)=="runtime error" ) then
@type( int_int_int(t(), e()) )
else
"type error"
)};
</pre>
<p>
関数が自動リフトされるので、フィボナッチ関数の型を調べることができます。
</p>
<pre>
>> 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
</pre>
<p>
この定義で <tt>fib(100000000000000)</tt> を <tt>@value</tt> レイヤで普通に計算して、
結果の型を見る、というのでは時間がいくらあっても足りません。
いったん <tt>@type</tt> のメタ情報の世界に移ってから計算できるのが、レイヤ機能の肝です。
</p>
<p>
正確には、この定義で <tt>@type</tt> レイヤに移ると fib("int") を無限に呼び出し続けて止まらなくなるのですが、
そこは、自動メモ化による再帰検出でボトム値を返す機能によって、うまく止まっています。
</p>
<p>
それでも上手く型計算ができない(あるいはすごく遅くなる)ような複雑な関数があるかもしれません。
仕方がないので、型情報をアノテーションとしてつけてあげることも可能です。
</p>
<pre>
@type f = int_int_int;
def f(x,y) { ...とても型を計算できないくらい複雑な定義... };
</pre>
<p>
これが、レイヤ指定変数定義の典型的な使い道です。
</p>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">Macro Layers</span>
<script>explorer.outline.addDecl('Macro Layers');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
Polemy 言語組み込みのレイヤは <code>@value</code> と <code>@macro</code> の二つです。
(内部的にはもういくつかありますが、ユーザから直接は使えません。)
<code>@value</code> は、「普通に」普通のセマンティクスでプログラムを実行するレイヤでした。
<code>@macro</code> は、実は、<code>@value</code> よりも前に実行されるレイヤで、
「プログラムを実行するとその構文木を返す」というセマンティクスで動きます。
</p>
<p>
動きとしてはこうです。
</p>
<ol>
<li>関数呼び出し時(とトップレベル環境の実行開始時)に、
まず、<code>@macro</code> レイヤでコードを実行。</li>
<li>返ってきた構文木を、その関数を呼び出したときのレイヤで実行。</li>
</ol>
<p>
<code>@macro</code> レイヤも所詮ただのレイヤですので、
上で説明した方法で <code>@macro</code> レイヤに関数などを登録しておくことで、
構文木の生成をいじることが可能です。まさにマクロ。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">概要</span>
<script>explorer.outline.addDecl('概要');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
samples/macro.pmy にいくつか使い方サンプルが置いてありますので、詳しくはそちらをどうぞ。
</p>
<pre>
>> @macro( twice(print("Hello")) )
{
pos: {lineno:1, column:9, filename:<REPL>},
args: [ { pos: {lineno:1, column:15, filename:<REPL>},
args: [{pos:{lineno:1, column:21, filename:<REPL>},
is:Str,
data:Hello}],
is: App,
fun: {pos:{lineno:1, column:15, filename:<REPL>}, is:Var, name:print}}
],
is: App,
fun: {pos:{lineno:1, column:9, filename:<REPL>}, is:Var, name:twice}
}
</pre>
<p>
詳細は気にしなくて構いませんが、とにかく、<tt>@macro</tt> レイヤでは、
基本的には、コードを実行するとそのコードの構文木がでてきます。
この挙動は <tt>@macro</tt> レイヤの変数をセットすることで、カスタマイズできます。
</p>
<pre>
>> @macro twice(x) { x; x } in twice(print("Hello"))
Hello
Hello
Hello
</pre>
<p>
(3回出力されてますが、3個目は <tt>print(x)</tt> の返値は <tt>x</tt> なので、
それがREPLによって印字されているだけです。)
<tt>@macro</tt> レイヤで <tt>in</tt> 以降を実行すると、<tt>print("Hello")</tt> という式を表す構文木が作られ、
それが <tt>twice</tt> 関数に渡されます。<tt>twice</tt> の中身も <tt>@macro</tt> レイヤで実行されるので、
構文木を作ろうとしますが、変数 <tt>x</tt> には <tt>@macro</tt> レイヤで値が入っているので、
その値を読み取って構文木を作成します。
結果として、2回 <tt>print("Hello")</tt> する構文木が作られて、
その後で、それが <tt>@value</tt> レイヤで実行されています。
</p>
<p>
本当にベタに構文木を作るだけなので、変数名の衝突などなどは気にしません。「衛生的でない」マクロです。
</p>
<pre>
@macro LetItBe(x, y) { var <b>it</b> = x; y }; <font color=green># y の中で変数 it が使える</font>
print( LetItBe("myself", "when I find " ~ <b>it</b> ~ " in times of trouble") );
</pre>
<p>
変数名に気をつけるには、組み込み関数 <tt>gensym()</tt> を使って頑張って下さい。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">レイヤ切り替え</span>
<script>explorer.outline.addDecl('レイヤ切り替え');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
他のレイヤ同様、<tt>@macro</tt> レイヤを実行中に <tt>@layer( ... )</tt> 構文を使うことで、
別のレイヤでコードを動かすこともできます。よく使う例は、<tt>@value</tt>
レイヤに移ることで構文木を普通に計算して色々プログラム的にいじる用途です。
</p>
<pre>
@macro reverseArgs(e) {<b>@value</b>(
def rev(xs, acc) {
case xs when {car:x, cdr:xs}: rev(xs, {car:x, cdr:acc}) when {}: acc
};
case @macro(e)
when {is:"App", fun:f, args:as}: {is:"App", fun:f, args:rev(as,{})}
when e: e
)};
print( reverseArgs(1-2) ); <font color=green># 2-1 == 1</font>
</pre>
<p>
<tt>reverseArgs</tt> は、関数呼び出しの構文木の、引数の順番を逆転する関数です。
<tt>@macro(e)</tt> によってマクロレイヤにセットされている構文木引数を取り出し、
それを <tt>@value</tt> レイヤによる普通の計算プログラムで操作しています。
<tt>@macro(...)</tt> はいわゆる「準クオート (quasiquote)」、
<tt>@value(...)</tt> は「逆クオート (unquote)」にちょっと近いかもしれません。
</p>
<p>
<tt>@layer(...)</tt> だけでなく、関数のレイヤ指定引数なども同様に使うことができるので、
一部の引数は <tt>@macro</tt>、一部の引数は <tt>@value</tt> レイヤで受け取る関数を書くなど、
さらに色々面白いことが可能です。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">構文木の構造</span>
<script>explorer.outline.addDecl('構文木の構造');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
構文木がどのようなテーブルで渡されてくるかについては、ソースドキュメントの
<a href="http://www.kmonos.net/repos/polemy/doc/tip/doc/ast.html">polemy.ast</a>
のページをご覧下さい。例えば変数名を表す <code>Var</code> クラスには、
継承の分も合わせて
<tt><a href="http://www.kmonos.net/repos/polemy/doc/tip/doc/failure.html">LexPosition</a> pos;</tt>
と <tt>string name;</tt> の2つのメンバがあるので
</p>
<pre>
{ is: "Var",
pos: {filename:"foo.pmy", lineno:123, column:45},
name: "x" }
</pre>
<p>
こんな感じのテーブルになります。
クラス名が <tt>is</tt> フィールドに、メンバ変数はそのままの名前で入ります。
配列メンバは cons リストになって入ってきます。
自分で構文木を作る時は、<tt>pos</tt> フィールドだけは省略しても構いません。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">微妙なところ1</span>
<script>explorer.outline.addDecl('微妙なところ1');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
ここまで、<tt>@macro</tt> が本当にただの1レイヤと説明してきましたが、
実はちょっとトリックが潜んでいます。
</p>
<pre>
>> @macro twice(x) {x; x} in twice(<b>@value</b>(print("Hello")))
Hello
Hello
Hello
</pre>
<p>
先ほどの例に <tt>@value</tt> を増やしたものですが、これでもやはり、Hello
が2回 print されるようになります。これは本来はおかしな話で、<tt>print("Hello")</tt>
は <tt>@value</tt> レイヤで実行されて値に落ちるはずなので、1回しか print されないはず。
</p>
<p>
実は、Polemy の中では、<tt>@macro</tt> レイヤと <tt>(rawmacro)</tt>
レイヤという二つの異なるマクロ用レイヤが動いています。
</p>
<ul>
<li><tt>(rawmacro)</tt> も <tt>@macro</tt> も、コードを動かすとその構文木を返す意味論。</li>
<li>ただし、<tt>(rawmacro)</tt> も <tt>@macro</tt> も、
<tt>@macro</tt> レイヤに値のセットされた変数をみつけたときは、
その変数という構文木を作るのではなく、変数の内容を展開。</li>
<li>また <tt>@macro</tt> は、
レイヤ指定式を見ると実行レイヤを切り替て、構文木生成モードをやめてしまう。</li>
<li><tt>(rawmacro)</tt> は、
レイヤ指定式を見ても実行レイヤを切り替えないで構文木にする。</li>
</ul>
<p>
ユーザーから直接 <tt>(rawmacro)</tt> は呼べませんが、
「関数やトップレベル実行開始前のマクロ処理は <tt>(rawmacro)</tt> で実行開始」
「<tt>@macro</tt> レイヤ以外で呼び出した関数の仮引数に <tt>@macro</tt> がついていたら、
その実引数は <tt>(rawmacro)</tt> で実行」
という2つのタイミングで <tt>(rawmacro)</tt> が動き出します。
<tt>(rawmacro)</tt> が <tt>@macro</tt> レイヤから変数を見つけてマクロし始める時に、
そこで <tt>@macro</tt> に動作が移ります。
</p>
<p>
こうなっているのは、全部がレイヤ指定式に反応する <tt>@macro</tt> の動作だと、
レイヤを使ったプログラムが全て <tt>@value</tt> 実行時ではなく、
マクロ展開の時点で動き始めてしまって、おかしなことになるためです。
色々考えた結果、とりあえずこの中途半端な混合が具合がよいのではないかということになりました。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">微妙なところ2</span>
<script>explorer.outline.addDecl('微妙なところ2');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
「関数実行開始時に、まずマクロレイヤを実行」と書きましたが、この時、関数内関数まで辿りにいくので、
何重にもネストした関数を使っていると、内側の関数は、何重にもマクロ展開が走ってしまいます。
これはなにかおかしい気がしますね。Scheme などはどうなっているのか調べないと…。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">微妙なところ3</span>
<script>explorer.outline.addDecl('微妙なところ3');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
これはエラーになります。
</p>
<pre>
>> let _ = (@macro twice(x) {x;x} in twice(print("Hello")))
polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\value.d(109):
[<REPL>:2:35] 'twice' is not set in @value layer
</pre>
<p>
どういうことかというと、<tt>@macro</tt> で定義したマクロはいつから使えるようになるかという話で、
この <tt>@macro twice(x) {x;x} in ...</tt> の部分は <tt>@value</tt> レイヤの式なので、
まずこの式全体のマクロ展開が終わったあとにしか実行されないのです。<tt>twice</tt>
がマクロと見なされはじめるのは、<tt>@macro</tt> 実行が終わった後。
なので、
例えば <tt>twice(print("Hello"))</tt> の部分を無名関数にラップしてやれば、
マクロ展開を遅らせられて、 ちゃんと実行ができます。
</p>
<p>
これだと余りにも不便なので、関数のトップレベルの変数宣言式の列についてだけは、
<tt>@macro</tt> と <tt>@value</tt> の評価を交互にインターリーブするようにしました。
「関数やREPLのトップレベルの最初に宣言したマクロだけは、その関数内で即座に使える」わけです。
これも Scheme の let-syntax などなどの動きを調べて勉強しないと…。
</p>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">Built-in Primitives</span>
<script>explorer.outline.addDecl('Built-in Primitives');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd><p>
組み込み関数・変数の一覧。
</p>
<script>explorer.outline.incSymbolLevel();</script>
<dl>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">テーブル操作</span>
<script>explorer.outline.addDecl('テーブル操作');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd> <table> <tr><th>{}</th> <td>()</td> <td>空のテーブルを作る</td></tr>
<tr><th>.</th> <td>(t, s)</td> <td>テーブル t の名前 s のフィールドの値を取得。なければ <tt>undefined</tt></td></tr>
<tr><th>.?</th> <td>(t, s)</td> <td>テーブル t に名前 s のフィールドがあれば 1、なければ 0</td></tr>
<tr><th>.=</th> <td>(t, s, v)</td> <td>テーブル t を親に持ち、名前 s のフィールドに v が入ったテーブルを作る</td></tr>
</table>
</dd>
<br />
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">制御フロー</span>
<script>explorer.outline.addDecl('制御フロー');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd> <table> <tr><th>if</th> <td>(n, ft, fe)</td> <td>n が非 0 なら <tt>ft()</t>、0 なら <tt>fe()</tt> を実行</td></tr>
</table>
</dd>
<br />
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">演算</span>
<script>explorer.outline.addDecl('演算');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd> <table> <tr><th>+</th> <td>(n, m)</td> <td>整数 n と整数 m を足して返す</td></tr>
<tr><th>-</th> <td>(n, m)</td> <td>整数の引き算</td></tr>
<tr><th>*</th> <td>(n, m)</td> <td>整数の掛け算</td></tr>
<tr><th>/</th> <td>(n, m)</td> <td>整数の割り算</td></tr>
<tr><th>%</th> <td>(n, m)</td> <td>整数の剰余</td></tr>
<tr><th>&&</th> <td>(n, m)</td> <td>整数 n と m が両方非 0 なら 1、それ以外では 0</td></tr>
<tr><th>||</th> <td>(n, m)</td> <td>整数 n と m がどちらか非 0 なら 1、それ以外では 0</td></tr>
<tr><th>~</th> <td>(a, b)</td> <td>a と b を文字列化して結合</td></tr>
<tr><th><</th> <td>(a, b)</td> <td>a と b を比較</td></tr>
<tr><th><=</th> <td>(a, b)</td> <td>a と b を比較</td></tr>
<tr><th>></th> <td>(a, b)</td> <td>a と b を比較</td></tr>
<tr><th>>=</th> <td>(a, b)</td> <td>a と b を比較</td></tr>
<tr><th>==</th> <td>(a, b)</td> <td>a と b を比較</td></tr>
<tr><th>!=</th> <td>(a, b)</td> <td>a と b を比較</td></tr>
</table>
<p>
注意点として、作者の趣味の問題で、<tt>&&</tt> と <tt>||</tt> は short-circuit 評価をしません。
整数演算の種類が少ないのは、D 言語の std.bigint がビット演算などをサポートしてないためです。
文字列が結合しかできないのは、単に手抜きです。
</p>
</dd>
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">外部とのやりとり</span>
<script>explorer.outline.addDecl('外部とのやりとり');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd> <table> <tr><th>print</th> <td>(a)</td> <td>a を文字列化標準出力に改行付きで表示して、a を返す</td></tr>
<tr><th>argv</th> <td></td> <td>スクリプトに渡された引数文字列のconsリスト</td></tr>
<tr><th>gensym</th> <td>()</td> <td>エセgensym。変数名として他とかぶらなそうな文字列を返します</td></tr>
<tr><th>rand</th> <td>(n)</td> <td>0 以上 n 未満の自然数を31bit以内でランダムに生成します</td></tr>
</table>
</dd>
<br />
<script>explorer.outline.writeEnabled = true;</script>
<dt><span class="decl">
<span class="currsymbol">データ型判定</span>
<script>explorer.outline.addDecl('データ型判定');</script>
</span></dt>
<script>explorer.outline.writeEnabled = false;</script>
<dd> <table> <tr><th>_isint</th> <td>(a)</td> <td>a が整数なら 1、でなければ 0</td></tr>
<tr><th>_isstr</th> <td>(a)</td> <td>a が文字列なら 1、でなければ 0</td></tr>
<tr><th>_isfun</th> <td>(a)</td> <td>a が関数なら 1、でなければ 0</td></tr>
<tr><th>_istbl</th> <td>(a)</td> <td>a がテーブルなら 1、でなければ 0</td></tr>
<tr><th>_isbot</th> <td>(a)</td> <td>a が未定義値なら 1、でなければ 0</td></tr>
</table>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</dd>
</dl>
<script>explorer.outline.decSymbolLevel();</script>
</td></tr>
<tr><td id="docfooter">
Page was generated with
<img src="candydoc/img/candydoc.gif" style="vertical-align:middle; position:relative; top:-1px">
on Sun Nov 28 07:29:42 2010
</td></tr>
</table>
</div>
<script>
explorer.packageExplorer.addModule("index");
explorer.packageExplorer.addModule("main");
explorer.packageExplorer.addModule("tricks.tricks");
explorer.packageExplorer.addModule("tricks.test");
explorer.packageExplorer.addModule("polemy._common");
explorer.packageExplorer.addModule("polemy.failure");
explorer.packageExplorer.addModule("polemy.layer");
explorer.packageExplorer.addModule("polemy.fresh");
explorer.packageExplorer.addModule("polemy.lex");
explorer.packageExplorer.addModule("polemy.parse");
explorer.packageExplorer.addModule("polemy.ast");
explorer.packageExplorer.addModule("polemy.value");
explorer.packageExplorer.addModule("polemy.valueconv");
explorer.packageExplorer.addModule("polemy.eval");
explorer.packageExplorer.addModule("polemy.runtime");
explorer.packageExplorer.addModule("polemy.repl");</script>
</body></html>