1 Ddoc
2 $(DDOC_AUTHORS k.inaba)
3 $(DDOC_LICENSE NYSL 0.9982 (http://www.kmonos.net/nysl/))
4
5 <p>
6 左のサイドバーの "Package" タブをクリックすると実装のソースのドキュメントが読めます。
7 </p>
8 <p>
9 このファイルは、言語仕様などの、やや辞書的な説明です。<br />
10 もっとざっくりとした、言語デザインの方向性の魂的なものについては、
11 「メタプログラミングの会」の発表スライドをご覧下さい。
12 </p>
13 <p>
14 あと、 やたらとマクロの章が長くなっていますが、 この部分は、
15 レイヤ機能を入れたら自動的にすごく自然にマクロが入るなーと思って、
16 おまけで実装してみた程度のものです。
17 あんまり重要ではないので、適当にスルーして下さいませ。
18 単に、適当に入れたら適当で微妙な部分が多く残ってしまったので注意書きが増えているだけで…。
19 </p>
20
21 $(DDOC_MEMBERS
22
23 $(SECTION Syntax, $(SECBODY
24 <p>
25 文法について。
26 字句解析がわりと適当なので、
27 変数宣言の変数名のところに、数字を変数名として使えて参照できない変数が作れたり、
28 予約語は予約語として解釈され得ないところでは普通に変数名として使えちゃったりして、
29 偶にとんでもない見かけのソースが構文解析通りますが、気にしないで適当に使って下さい。
30 </p>
31
32 $(DDOC_MEMBERS
33
34 $(SECTION 文字コード, $(SECBODY
35 <p>
36 UTF-8 のみ対応です。
37 </p>
38 ))
39
40 $(SECTION コメント, $(SECBODY
41 <p>
42 行コメントは <tt>#</tt> から改行までです。
43 </p>
44 <p>
45 ブロックコメントはありません。
46 </p>
47 ))
48
49 $(SECTION BNF, $(SECBODY
50 <pre>
51 ID ::= 適当に識別子っぽい文字列
52 LAYER ::= "@" ID
53
54 E ::=
55 $(D_COMMENT # 変数宣言)
56 | DECL "=" E (";"|"in") E
57 | DECL "(" PARAMS ")" "{" E "}" (";"|"in") E
58 | DECL "=" E
59 | DECL "(" PARAMS ")" "{" E "}"
60
61 where DECL ::= ("var"|"let"|"def"|LAYER) ID | "@" LAYER
62
63 $(D_COMMENT # リテラル)
64 | INTEGER $(D_COMMENT # 非負整数)
65 | STRING $(D_COMMENT # "" でくくった文字列。\" と \\ は使える)
66 | "{" ENTRYS "}" $(D_COMMENT # テーブル)
67 | "fun" "(" PARAMS ")" "{" E "}" $(D_COMMENT # 無名関数)
68 | "λ" "(" PARAMS ")" "{" E "}" $(D_COMMENT # 無名関数)
69
70 $(D_COMMENT # 関数呼び出し)
71 | E "(" ARGS")"
72
73 where ARGS ::= E "," ... "," E
74 PARAMS ::= (ID|LAYER)+ "," ... "," (ID|LAYER)+
75 ENTRYS ::= ID ":" E "," ... "," ID ":" E
76
77 $(D_COMMENT # 演算子など)
78 | "(" E ")" $(D_COMMENT # ただの括弧)
79 | "..." $(D_COMMENT # これを実行するとdie)
80 | E BINOP E $(D_COMMENT # 二項演算子いろいろ)
81 | E "." ID $(D_COMMENT # テーブルのフィールドアクセス)
82 | E ".?" ID $(D_COMMENT # テーブルにフィールドがあるか否か)
83 | E "{" ENTRYS "}" $(D_COMMENT # テーブル拡張)
84 | "if" E ("then"|":"|"then" ":") E
85 | "if" E ("then"|":"|"then" ":") E "else" ":"? E
86
87 $(D_COMMENT # パターンマッチ)
88 | "case" E ("when" PATTERN ":" E )*
89
90 where PATTERN ::= 式がだいたいなんでも書ける気がする
91
92 $(D_COMMENT # レイヤ指定実行)
93 | LAYER "(" E ")"
94 </pre>
95 ))
96
97 $(SECTION 糖衣構文, $(SECBODY
98 <p>
99 演算子というものはありません。内部的には全て関数呼び出し構文に書き換えられています。<tt>if</tt> もです。
100 <br/>
101 パターンマッチも全部 <tt>if</tt> と <tt>==</tt> と <tt>&&</tt> と
102 <tt>.</tt> と <tt>.?</tt> を使った関数呼び出し式に書き換えられていますが、
103 規則の詳細を説明するのが面倒なので適当に想像して下さい。
104 他の書き換えはこんな感じです。
105 </p>
106 <pre>
107 if E then E ⇒ if( E, fun(){E}, fun(){} )
108 if E then E else E ⇒ if( E, fun(){E}, fun(){E} )
109 E BINOP E ⇒ BINOP(E, E)
110 { ENTRIES } ⇒ {}{ ENTRIES }
111 {} ⇒ {}()
112 E {ID:E, ...} ⇒ .=(E, ID, E) { ... }
113 </pre>
114 <p>
115 変数宣言に色々ありますが、<tt>let</tt> と <tt>var</tt> と <tt>def</tt> は同じ扱いで、
116 <tt>in</tt> と <tt>;</tt> は同じ扱いです。つまり
117 </p>
118 <pre>
119 let x = E in E
120 var x = E in E
121 def x = E in E
122 let x = E ; E
123 var x = E ; E
124 def x = E ; E
125 </pre>
126 <p>
127 以上のどれも同じ意味なので、なんとなく関数型っぽく書きたい気分の日は <tt>let in</tt> を、
128 手続き型っぽく書きたい気分の日は <tt>var ;</tt> を使うとよいと思います。
129 <tt>if then else</tt> も微妙にコロンがあったりなかったりバリエーションがありますが好みで使います。
130 </p>
131 <p>
132 関数を宣言するときは、<tt>fun</tt> や <tt>λ</tt> を省略できます。
133 以下の書き換えが行われます。
134 </p>
135 <pre>
136 def f( ARGS ) { E }; E ⇒ def f = fun(ARGS){E}; E
137 </pre>
138 <p>
139 他に、もっと手続き型っぽくための書き換え色々
140 </p>
141 <pre>
142 fun () { E; E; E } ⇒ fun () { let _ = E in let _ = E in E }
143 fun () { var x = 100 } ⇒ fun () { var x = 100; x }
144 fun () { var x = 100; } ⇒ fun () { var x = 100; x }
145 fun () { } ⇒ fun () { "(empty function body)" }
146 </pre>
147 <p>
148 中身が空の関数に何を返させるかは適当です。今はとりあえず適当に文字列返してます。
149 </p>
150 ))
151
152 $(SECTION 変数のスコープ規則, $(SECBODY
153 <p>
154 基本的には、let によって常識的な感じに変数のスコープがネストします。
155 </p>
156 <pre>
157 let x=21 in let x=x+x in x $(D_COMMENT # 42)
158 </pre>
159 <p>
160 一方で、"let rec" のような特別な構文はありませんが、
161 </p>
162 <pre>
163 let f = fun(x) { if x==0 then 1 else x*f(x-1) } in f(10) $(D_COMMENT # 3628800)
164 </pre>
165 <p>
166 再帰的な関数定義なども、おそらく意図されたとおりに動きます。
167 内部の詳細は、諸般の事情により、
168 マジカルで破壊的なスコープ規則になっているのですが、
169 同名の変数を激しく重ねて使ったりしなければ、
170 だいたい自然な動きをすると思います、たぶん、はい。
171 </p>
172 <p>
173 ひとつだけ不可思議な動きをするのは、以下のケースです。
174 </p>
175 <pre>
176 let x = 1 in
177 let f = fun() {x} in
178 let x = 2 in
179 f() $(D_COMMENT # 2!!)
180 </pre>
181 <p>
182 let-in を縦にチェインしたときだけ、同名変数を破壊的に上書きします
183 (再帰関数の定義が"うまく"いっているのはこの上書きのためです)。
184 なんでこんなことになっているかというと、
185 後で説明する「レイヤ」を使ったときに
186 <tt>let foo = ... in @lay foo = ... in ...</tt>
187 で他レイヤに重ね書きするため、のつもりです。詳しくは後で。
188 </p>
189 ))
190 )
191 ))
192
193
194
195
196 $(SECTION Basic Features, $(SECBODY
197 <p>
198 特に特徴的でもない部分を簡単にまとめ。
199 </p>
200 <ul>
201 <li>静的型システムはありません。</li>
202 <li>"ほぼ" 純粋関数型言語です。変数やテーブルのフィールドの破壊的な書き換えはできません。<br/>
203 ただし、組み込み関数(<tt>print</tt>)と、変数のスコープ規則のマジカルな片隅に副作用があります。</li>
204 </ul>
205 <p>
206 静的型システムがないのは意図的ですが、破壊的代入がないのは、単に実装がめんどかっただけなので、
207 今後何か増えるかもしれません。増えないかもしれません。
208 </p>
209 $(DDOC_MEMBERS
210 $(SECTION データ型, $(SECBODY
211 <p>
212 以下のデータ型があります。
213 </p>
214 <ul>
215 <li>整数: <tt>0</tt>, <tt>123</tt>, <tt>456666666666666666666666666666666666666789</tt>, ...</li>
216 <li>文字列: <tt>"hello, world!"</tt>, ...</li>
217 <li>関数: <tt>fun(x){x+1}</tt></li>
218 <li>テーブル: <tt>{car: 1, cdr: {car: 2, cdr: {}}}</tt></li>
219 <li>ボトム: (特殊なケースで作られます。「レイヤ」の説明参照のこと。)</li>
220 </ul>
221 <p>
222 関数はいわゆる「クロージャ」です。静的スコープで外側の環境にアクセスできます。
223 テーブルはいわゆるプロトタイプチェーンを持っていて、
224 自分にないフィールドの場合は親に問い合わせが行く感じになっていますが、
225 フィールドの書き換えがないので、これは特に意味ないかもしれない…。
226 </p>
227 <p>
228 また、リストを扱うために、いわゆる「cons リスト」を使います。
229 空リストを <tt>{}</tt>、1個以上要素があるものを <tt>{car: 先頭要素, cdr: 二番目以降のリスト}</tt>
230 という形で。この形でリストを扱わなければならないという決まりはありませんが、
231 この形は特別扱いされて <tt>print</tt> で綺麗に出力されたりします。
232 </p>
233 ))
234 $(SECTION パターンマッチ, $(SECBODY
235 <p>
236 適当に実装されたパターンマッチがあります。
237 リストの 2n 番目と 2n+1 番目を足して長さを半分にする関数:
238 </p>
239 <pre>
240 def adjSum(lst)
241 {
242 case lst
243 when {car:x, cdr:{car: y, cdr:z}}: {car: x+y, cdr: adjSum(z)}
244 when {car:x, cdr:{}}: lst
245 when {}: {}
246 }
247 </pre>
248 <p>
249 動かすときには、処理系がそれっぽい if-then-else に展開しています。
250 <tt>when</tt> を上から試していって、最初にマッチしたところを実行します。
251 どれにもマッチしないとエラーでプログラム終了します。
252 </p>
253 <pre>
254 PAT ::= "_" $(D_COMMENT # ワイルドカード)
255 | ID $(D_COMMENT # 変数パターン)
256 | "{" ID ":" PAT "," ... "," ID : PAT "}" $(D_COMMENT # テーブルパターン)
257 | E $(D_COMMENT # 値パターン)
258 </pre>
259 <p>
260 変数パターンは常にマッチして、値をその変数に束縛します。
261 ワイルドカードも常にマッチしますが、変数束縛しません。
262 値パターンは、任意の式が書けます。その式を評価した結果と <tt>==</tt> ならマッチします。
263 外で束縛された変数を値パターンとして配置、は直接はできないので
264 </p>
265 <pre>
266 var x = 123;
267 case foo
268 when {val: x+0}: ... $(D_COMMENT # これは {val:123} と同じ)
269 when {val: x}: ... $(D_COMMENT # これは任意の foo.?val なら常にマッチ)
270 </pre>
271 <p>
272 適当にちょっと複雑な式にしてやるとよいかも(裏技)。
273 </p>
274 <p>
275 テーブルパターンは、書かれたキーが全てあればマッチします。
276 <tt>{a: _}</tt> は、<tt>.a</tt> を持ってさえいればマッチするので、
277 <tt>{a: 123, b: 456}</tt> なんかにもマッチします。
278 なので、リストに対するパターンを書くときには、car/cdr の場合を先に書かないと
279 <tt>when {}</tt> を上に書くと全部マッチしちゃいます。注意。
280 </p>
281 ))
282 )
283 ))
284
285
286
287
288
289 $(SECTION Layers, $(SECBODY
290 <p>
291 この言語の唯一の特徴的な部分は、「レイヤ」機能です。
292 </p>
293 <p>
294 ひとつのコードに複数の「意味」を持たせるのが、レイヤ機能の目的です。
295 </p>
296 $(DDOC_MEMBERS
297 $(SECTION 概要, $(SECBODY
298 <p>
299 普通に Polemy のコードを動かすと、そのコードは「<tt>@value</tt> レイヤ」で動作します。
300 インタプリタで実験。
301 </p>
302 <pre>
303 $ bin/polemy
304 Welcome to Polemy 0.1.0
305 >> 1 + 2
306 3
307 </pre>
308 この、普通に、数字の 1 は数字の 1 として、2 は 2 として、足し算は足し算として実行するのが、
309 「<tt>@value</tt> レイヤ」です。
310 レイヤを明示的に指定するには、<tt>レイヤ名( ... )</tt> という構文を使います。
311 $(RED $(B レイヤ指定式)) と読んでいます。
312 つまり、さっきのコードは以下のようにも書けます。
313 <pre>
314 >> @value( 1 + 2 )
315 3
316 </pre>
317 他のレイヤで動かしてみましょう。適当に。「<tt>@hoge</tt> レイヤ」で。
318 <pre>
319 >> @hoge( 3 )
320 polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(138):
321 [<REPL>:4:8] lift function for @hoge is not registered
322 </pre>
323 <p>
324 エラーになりました。Polemy のインタプリタは、起動時には、<tt>@value</tt>
325 レイヤでのコードの意味しか知りません。<tt>@hoge</tt> レイヤでは <tt>3</tt>
326 というのがどんな意味なのか、わかりません!というエラーが出ています。
327 </p>
328 <p>
329 これを教えてあげるためには、<tt>@hoge</tt> レイヤの $(RED $(B リフト関数)) というものを定義します。
330 </p>
331 <pre>
332 >> @@hoge = fun(x){ x*2 }
333 (function:1bdc5c0:1ba8580)
334 </pre>
335 <p>
336 <tt>@hoge</tt> レイヤでは、<tt>1</tt> というコードの意味は <tt>2</tt>、
337 <tt>2</tt> というコードの意味は <tt>4</tt>、…、という、全部「2倍した意味」を持っていることにします。
338 「<tt>@ レイヤ名 = ...</tt>」 という構文を使います。
339 ここには、「<tt>@value</tt> レイヤでの値 <tt>x</tt> は <tt>@hoge</tt> レイヤではどういう意味になるか?」
340 を計算して返す関数を登録します。
341 これで、Polemy にも、<tt>@hoge</tt> レイヤの意味がわかるようになりました。
342 </p>
343 <pre>
344 >> @hoge( 3 )
345 6
346 </pre>
347 <p>
348 では、1+2 を <tt>@hoge</tt> レイヤで動かしてみましょう。
349 </p>
350 <pre>
351 >> @hoge( 1 + 2 )
352 polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(466):
353 [<REPL>:3:7] only @value layer can call native function: +
354 [<REPL>:3:7] +
355 </pre>
356 <p>
357 まだエラーですね。これは要するに "+" の意味がわからない、と言っています。
358 $(RED $(B レイヤ指定変数定義式)) で、"+" の意味を教えてあげます。
359 </p>
360 <pre>
361 >> @hoge "+" = fun(x, y) {x}
362 (function:182eca0:18435e0)
363 >> @hoge( 3 + 4 )
364 6
365 </pre>
366 <p>
367 できました。
368 </p>
369 <p>
370 他の組み込み関数の意味も決めてみましょう。この <tt>@hoge</tt> レイヤでは、
371 引き算のつもりで書いたコードが、掛け算になってしまうのだ!
372 </p>
373 <pre>
374 >> @hoge "-" = fun(x, y) {x * y}
375 (function:1b4c6a0:1b4fbe0)
376 >> @hoge( 5 - 6 )
377 polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\eval.d(469):
378 [<REPL>:3:24] only @value layer can call native function: *
379 [<REPL>:3:24] *
380 [<REPL>:4:8] -
381 </pre>
382 <p>
383 5、の意味は 10 で 6 の意味は 12 なので、10 - 12 と見せかけて掛け算して 120 が返るのだ!
384 と思いきや、エラーになってしまいました。なぜでしょう。それは、この "-" の定義、
385 <code>fun(x, y) {x * y}</code> 自体が、<tt>@hoge</tt> レイヤで実行されるからです。
386 掛け算はまだ定義していません。
387 </p>
388 <p>
389 ここは、「普通の」意味の掛け算を使いたいのです。
390 この部分については、<tt>@value</tt> レイヤで計算して欲しい。
391 そんなときは、レイヤ指定式を使います。
392 </p>
393 <pre>
394 >> @hoge "-" = fun(x, y) {$(B @value(@hoge(x) * @hoge(y)))}
395 (function:1b086c0:1b4fbe0)
396 >> @hoge( 5 - 6 )
397 120
398 </pre>
399 <p>
400 できました。掛け算は、<tt>@value</tt> レイヤの意味で実行します。
401 各変数は、<tt>@hoge</tt> レイヤで計算された意味を使います、という意味になります。
402 </p>
403 ))
404 $(SECTION 関数の自動リフト, $(SECBODY
405 <p>
406 続きです。ちょっと関数を定義してみました。
407 </p>
408 <pre>
409 >> def twoMinus(x,y,z) { x - y - z }
410 (function:1b26420:1b4fbe0)
411 >> twoMinus(1,2,3)
412 -4
413 </pre>
414 <p>
415 <tt>@value</tt> レイヤで実行すると、当然、1 から 2 と 3 を引いて、-4 です。
416 </p>
417 <pre>
418 >> @hoge( twoMinus(1,2,3) )
419 48
420 </pre>
421 <p>
422 <tt>@hoge</tt> レイヤだと、2 と 4 と 6 を掛け算するので、結果は 48 です。
423 </p>
424 <p>
425 1, 2, 3 のような値と、+ や - のような組み込み関数については、
426 「<tt>@hoge</tt> レイヤでの意味」をレイヤを定義する人が決めてやる必要があります。
427 でも、それさえ決めれば、あとはプログラム中で自分で定義した関数はすべて、
428 Polemy 側で自動的にそのレイヤでの意味で実行できるようになります。
429 </p>
430 <p>
431 レイヤ指定変数定義を使って、変数の意味をそのレイヤでだけ上書きして、
432 違う意味を与えてやっても構いません。
433 </p>
434 <pre>
435 >> def twoMinus(x,y,z) { x - y - z } $(D_COMMENT # @value レイヤでの定義)
436 >> @hoge twoMinus(x,y,z) { 21 } $(D_COMMENT # @hoge レイヤでの定義)
437 >> twoMinus(1,2,3)
438 -4
439 >> @hoge( twoMinus(1,2,3) )
440 42
441 </pre>
442 <p>
443 こんな感じで。
444 </p>
445 ))
446 $(SECTION レイヤ指定引数, $(SECBODY
447 <p>
448 ここまでのサンプルでは、コードを書いた人が、レイヤ指定式で明示的にレイヤを切り替えていました。
449 $(RED $(B レイヤ指定引数)) を使うと、ライブラリ関数などを書くときに、
450 「この関数の第2引数は <tt>@hoge</tt> レイヤで計算して欲しい」
451 といった指定ができます。
452 </p>
453 <pre>
454 >> def f(x, y $(B @hoge)) { x + @hoge(y) }
455 >> f(1, 2)
456 5
457 </pre>
458 <p>
459 f の第2引数は、必ず <tt>@hoge</tt> レイヤで解釈されます。
460 </p>
461 <pre>
462 >> def ff(x, y $(B @hoge @value)) { x + @hoge(y) + @value(y) }
463 >> ff(1, 2)
464 7
465 </pre>
466 <p>
467 <tt>@hoge</tt> と <tt>@value</tt> の両方のレイヤで解釈して欲しい、という欲張りな人は、
468 レイヤ指定を複数並べて下さい。
469 </p>
470 <p>
471 なにもレイヤ指定がないと、$(RED $(B ニュートラルレイヤ指定)) と呼ばれ、
472 その関数の呼び出し側が解釈されていたレイヤと同じところにセットされます。
473 <tt>let</tt>, <tt>var</tt>, <tt>def</tt> による変数定義も同じで、
474 <tt>@hoge x = ...</tt> とレイヤを明示するとそのレイヤでの変数の意味が定義されますが、
475 <tt>let x = ...</tt> とレイヤ指定しないで書くと、現在解釈中のレイヤに定義、という動作をします。
476 </p>
477 ))
478 $(SECTION ボトムと自動メモ化, $(SECBODY
479 <p>
480 パターンマッチ失敗時と、"..." という式を実行したときと、再帰が無限に止まらなくなったとき、
481 には、Polemy のコードは実行時エラーで終了します……<tt>@value</tt> レイヤならば。
482 </p>
483 <p>
484 ユーザー定義レイヤでは、このような時にも実行時エラーにならず、
485 「$(RED $(B ボトム))」という特別な値がリフト関数に渡されます。
486 組み込みの <tt>_isbot</tt> 関数で、ボトムかどうか判定できます。
487 </p>
488 <p>
489 「再帰が無限に止まらなくなったとき」は、
490 ある引数で呼び出された関数が、return するよりも前にまた同じ引数で呼び出されたら、
491 ループしていると見なすことで判定しています。
492 これを判定する実装の副作用として、ユーザー定義のレイヤでは、関数は全てメモ化されています。
493 つまり、ある関数が2回同じ引数同じ環境で呼び出されたら、1回目の答えをキャッシュしておいて、
494 2回目は計算をせずに即座にキャッシュをひいて答えを返します。
495 </p>
496 ))
497 $(SECTION まとめ, $(SECBODY
498 <p>
499 まとめると、以下の機能があります。
500 </p>
501 <ul>
502 <li><tt>@@layer = fun(x) { ... } in ...</tt> で、
503 <tt>@value</tt> レイヤの値に別のレイヤでの意味を与えるリフト関数を定義</li>
504 <li><tt>@layer x = ... in ...</tt> で、そのレイヤでのその変数の意味を定義</li>
505 <li>どちらも let/var/def 式の特殊形なので、<tt>@@layer(x) { ... } in ...</tt> などの略記も可。</li>
506 <li>式の途中で @layer( ... ) と書くと、レイヤを明示的に切り替えられる</li>
507 <li>関数の仮引数に fun(x @layer){ ... } とレイヤを指定すると、
508 対応する実引数はそのレイヤで解釈される。</li>
509 </ul>
510 <p>
511 </p>
512 ))
513 $(SECTION 例, $(SECBODY
514 <p>
515 具体的な「値」のかわりに、その「メタ情報」を取り出して、それが処理によってどう変化するか、
516 といった情報を解析するのを主な用途として、この機能を作ってみました。
517 プログラムでよく使われる代表的なメタ情報は、「型」です。
518 サンプルとしては、sample/type.pmy をご覧下さい。以下、簡単な概略。
519 </p>
520 <pre>
521 @@type = fun(x) {
522 if( _isint(x) ) then "int"
523 else if( _isstr(x) ) then "str"
524 else if( _isbot(x) ) then "runtime error"
525 else "type error"
526 }
527 </pre>
528 <pre>
529 >> @type( 1 )
530 int
531 >> @type( 2 )
532 int
533 >> @type( "foo" )
534 str
535 </pre>
536 <p>
537 こんな風に、値をメタ情報へ抽象化するのが、リフト関数です。
538 </p>
539 <p>
540 型に抽象化したレイヤでの、組み込み関数の意味を考えましょう。
541 "+" は、"int" と "int" を足したら "int" を返す関数です。
542 それ以外なら"型エラー"を返します。そういう関数です。
543 </p>
544 <pre>
545 var int_int_int = fun (x, y) {@value(
546 var tx = @type(x);
547 var ty = @type(y);
548 if tx=="runtime error" then ty
549 else if ty=="runtime error" then tx
550 else if tx=="int" && ty=="int" then "int"
551 else "type error"
552 )};
553
554 @type "+" = int_int_int;
555 @type "-" = int_int_int;
556 @type "<" = int_int_int;
557 </pre>
558 <pre>
559 >> @type( 1 + 2 )
560 int
561 >> @type( 1 + "foo" )
562 type error
563 </pre>
564 <p>
565 「実行時エラーについては、それが起きなければ返すはずの型」を計算するという定義に、
566 ここではしています。さらに(ちょっと手抜きで int 以外を考えていない)if の型定義を考えると、
567 こんな雰囲気。
568 </p>
569 <pre>
570 @type "if" (c, t, e) {@value(
571 if( @type(c)=="int" || @type(c)=="runtime error" ) then
572 @type( int_int_int(t(), e()) )
573 else
574 "type error"
575 )}
576 </pre>
577 <p>
578 関数が自動リフトされるので、フィボナッチ関数の型を調べることができます。
579 </p>
580 <pre>
581 >> def fib(x) { if x<2 then 1 else fib(x-1)+fib(x-2) };
582 >> @type( fib(100000000000000) )
583 int
584 >> def gib(x) { if x<2 then 1 else gib(x-1)+gib(x-"str") };
585 >> @type( gib(100000000000000) )
586 type error
587 </pre>
588 <p>
589 この定義で <tt>fib(100000000000000)</tt> を <tt>@value</tt> レイヤで普通に計算して、
590 結果の型を見る、というのでは時間がいくらあっても足りません。
591 いったん <tt>@type</tt> のメタ情報の世界に移ってから計算できるのが、レイヤ機能の肝です。
592 </p>
593 <p>
594 正確には、この定義で <tt>@type</tt> レイヤに移ると fib("int") を無限に呼び出し続けて止まらなくなるのですが、
595 そこは、自動メモ化による再帰検出でボトム値を返す機能によって、うまく止まっています。
596 </p>
597 <p>
598 それでも上手く型計算ができない(あるいはすごく遅くなる)ような複雑な関数があるかもしれません。
599 仕方がないので、型情報をアノテーションとしてつけてあげることも可能です。
600 </p>
601 <pre>
602 @type f = int_int_int;
603 def f(x,y) { ...とても型を計算できないくらい複雑な定義... };
604 </pre>
605 <p>
606 これが、レイヤ指定変数定義の典型的な使い道です。
607 </p>
608 ))
609 )
610 ))
611
612
613 $(SECTION Macro Layers, $(SECBODY
614 <p>
615 Polemy 言語組み込みのレイヤは <code>@value</code> と <code>@macro</code> の二つです。
616 (内部的にはもういくつかありますが、ユーザから直接は使えません。)
617 <code>@value</code> は、「普通に」普通のセマンティクスでプログラムを実行するレイヤでした。
618 <code>@macro</code> は、実は、<code>@value</code> よりも前に実行されるレイヤで、
619 「プログラムを実行するとその構文木を返す」というセマンティクスで動きます。
620 </p>
621 <p>
622 動きとしてはこうです。
623 </p>
624 <ol>
625 <li>関数呼び出し時(とトップレベル環境の実行開始時)に、
626 まず、<code>@macro</code> レイヤでコードを実行。</li>
627 <li>返ってきた構文木を、その関数を呼び出したときのレイヤで実行。</li>
628 </ol>
629 <p>
630 <code>@macro</code> レイヤも所詮ただのレイヤですので、
631 上で説明した方法で <code>@macro</code> レイヤに関数などを登録しておくことで、
632 構文木の生成をいじることが可能です。まさにマクロ。
633 </p>
634
635 $(DDOC_MEMBERS
636 $(SECTION 概要, $(SECBODY
637 <p>
638 samples/macro.pmy にいくつか使い方サンプルが置いてありますので、詳しくはそちらをどうぞ。
639 </p>
640 <pre>
641 >> @macro( twice(print("Hello")) )
642 {
643 pos: {lineno:1, column:9, filename:<REPL>},
644 args: [ { pos: {lineno:1, column:15, filename:<REPL>},
645 args: [{pos:{lineno:1, column:21, filename:<REPL>},
646 is:Str,
647 data:Hello}],
648 is: App,
649 fun: {pos:{lineno:1, column:15, filename:<REPL>}, is:Var, name:print}}
650 ],
651 is: App,
652 fun: {pos:{lineno:1, column:9, filename:<REPL>}, is:Var, name:twice}
653 }
654 </pre>
655 <p>
656 詳細は気にしなくて構いませんが、とにかく、<tt>@macro</tt> レイヤでは、
657 基本的には、コードを実行するとそのコードの構文木がでてきます。
658 この挙動は <tt>@macro</tt> レイヤの変数をセットすることで、カスタマイズできます。
659 </p>
660 <pre>
661 >> @macro twice(x) { x; x } in twice(print("Hello"))
662 Hello
663 Hello
664 Hello
665 </pre>
666 <p>
667 (3回出力されてますが、3個目は <tt>print(x)</tt> の返値は <tt>x</tt> なので、
668 それがREPLによって印字されているだけです。)
669 <tt>@macro</tt> レイヤで <tt>in</tt> 以降を実行すると、<tt>print("Hello")</tt> という式を表す構文木が作られ、
670 それが <tt>twice</tt> 関数に渡されます。<tt>twice</tt> の中身も <tt>@macro</tt> レイヤで実行されるので、
671 構文木を作ろうとしますが、変数 <tt>x</tt> には <tt>@macro</tt> レイヤで値が入っているので、
672 その値を読み取って構文木を作成します。
673 結果として、2回 <tt>print("Hello")</tt> する構文木が作られて、
674 その後で、それが <tt>@value</tt> レイヤで実行されています。
675 </p>
676 <p>
677 本当にベタに構文木を作るだけなので、変数名の衝突などなどは気にしません。「衛生的でない」マクロです。
678 </p>
679 <pre>
680 @macro LetItBe(x, y) { var $(B it) = x; y }; $(D_COMMENT # y の中で変数 it が使える)
681 print( LetItBe("myself", "when I find " ~ $(B it) ~ " in times of trouble") );
682 </pre>
683 <p>
684 変数名に気をつけるには、組み込み関数 <tt>gensym()</tt> を使って頑張って下さい。
685 </p>
686 ))
687 $(SECTION レイヤ切り替え, $(SECBODY
688 <p>
689 他のレイヤ同様、<tt>@macro</tt> レイヤを実行中に <tt>@layer( ... )</tt> 構文を使うことで、
690 別のレイヤでコードを動かすこともできます。よく使う例は、<tt>@value</tt>
691 レイヤに移ることで構文木を普通に計算して色々プログラム的にいじる用途です。
692 </p>
693 <pre>
694 @macro reverseArgs(e) {$(B @value)(
695 def rev(xs, acc) {
696 case xs when {car:x, cdr:xs}: rev(xs, {car:x, cdr:acc}) when {}: acc
697 };
698 case @macro(e)
699 when {is:"App", fun:f, args:as}: {is:"App", fun:f, args:rev(as,{})}
700 when e: e
701 )};
702 print( reverseArgs(1-2) ); $(D_COMMENT # 2-1 == 1)
703 </pre>
704 <p>
705 <tt>reverseArgs</tt> は、関数呼び出しの構文木の、引数の順番を逆転する関数です。
706 <tt>@macro(e)</tt> によってマクロレイヤにセットされている構文木引数を取り出し、
707 それを <tt>@value</tt> レイヤによる普通の計算プログラムで操作しています。
708 <tt>@macro(...)</tt> はいわゆる「準クオート (quasiquote)」、
709 <tt>@value(...)</tt> は「逆クオート (unquote)」にちょっと近いかもしれません。
710 </p>
711 <p>
712 <tt>@layer(...)</tt> だけでなく、関数のレイヤ指定引数なども同様に使うことができるので、
713 一部の引数は <tt>@macro</tt>、一部の引数は <tt>@value</tt> レイヤで受け取る関数を書くなど、
714 さらに色々面白いことが可能です。
715 </p>
716 ))
717 $(SECTION 構文木の構造, $(SECBODY
718 <p>
719 構文木がどのようなテーブルで渡されてくるかについては、ソースドキュメントの
720 <a href="http://www.kmonos.net/repos/polemy/doc/tip/doc/ast.html">polemy.ast</a>
721 のページをご覧下さい。例えば変数名を表す <code>Var</code> クラスには、
722 継承の分も合わせて
723 <tt><a href="http://www.kmonos.net/repos/polemy/doc/tip/doc/failure.html">LexPosition</a> pos;</tt>
724 と <tt>string name;</tt> の2つのメンバがあるので
725 </p>
726 <pre>
727 { is: "Var",
728 pos: {filename:"foo.pmy", lineno:123, column:45},
729 name: "x" }
730 </pre>
731 <p>
732 こんな感じのテーブルになります。
733 クラス名が <tt>is</tt> フィールドに、メンバ変数はそのままの名前で入ります。
734 配列メンバは cons リストになって入ってきます。
735 自分で構文木を作る時は、<tt>pos</tt> フィールドだけは省略しても構いません。
736 </p>
737 ))
738 $(SECTION 微妙なところ1, $(SECBODY
739 <p>
740 ここまで、<tt>@macro</tt> が本当にただの1レイヤと説明してきましたが、
741 実はちょっとトリックが潜んでいます。
742 </p>
743 <pre>
744 >> @macro twice(x) {x; x} in twice($(B @value)(print("Hello")))
745 Hello
746 Hello
747 Hello
748 </pre>
749 <p>
750 先ほどの例に <tt>@value</tt> を増やしたものですが、これでもやはり、Hello
751 が2回 print されるようになります。これは本来はおかしな話で、<tt>print("Hello")</tt>
752 は <tt>@value</tt> レイヤで実行されて値に落ちるはずなので、1回しか print されないはず。
753 </p>
754 <p>
755 実は、Polemy の中では、<tt>@macro</tt> レイヤと <tt>(rawmacro)</tt>
756 レイヤという二つの異なるマクロ用レイヤが動いています。
757 </p>
758 <ul>
759 <li><tt>(rawmacro)</tt> も <tt>@macro</tt> も、コードを動かすとその構文木を返す意味論。</li>
760 <li>ただし、<tt>(rawmacro)</tt> も <tt>@macro</tt> も、
761 <tt>@macro</tt> レイヤに値のセットされた変数をみつけたときは、
762 その変数という構文木を作るのではなく、変数の内容を展開。</li>
763 <li>また <tt>@macro</tt> は、
764 レイヤ指定式を見ると実行レイヤを切り替て、構文木生成モードをやめてしまう。</li>
765 <li><tt>(rawmacro)</tt> は、
766 レイヤ指定式を見ても実行レイヤを切り替えないで構文木にする。</li>
767 </ul>
768 <p>
769 ユーザーから直接 <tt>(rawmacro)</tt> は呼べませんが、
770 「関数やトップレベル実行開始前のマクロ処理は <tt>(rawmacro)</tt> で実行開始」
771 「<tt>@macro</tt> レイヤ以外で呼び出した関数の仮引数に <tt>@macro</tt> がついていたら、
772 その実引数は <tt>(rawmacro)</tt> で実行」
773 という2つのタイミングで <tt>(rawmacro)</tt> が動き出します。
774 <tt>(rawmacro)</tt> が <tt>@macro</tt> レイヤから変数を見つけてマクロし始める時に、
775 そこで <tt>@macro</tt> に動作が移ります。
776 </p>
777 <p>
778 こうなっているのは、全部がレイヤ指定式に反応する <tt>@macro</tt> の動作だと、
779 レイヤを使ったプログラムが全て <tt>@value</tt> 実行時ではなく、
780 マクロ展開の時点で動き始めてしまって、おかしなことになるためです。
781 色々考えた結果、とりあえずこの中途半端な混合が具合がよいのではないかということになりました。
782 </p>
783 ))
784 $(SECTION 微妙なところ2, $(SECBODY
785 <p>
786 「関数実行開始時に、まずマクロレイヤを実行」と書きましたが、この時、関数内関数まで辿りにいくので、
787 何重にもネストした関数を使っていると、内側の関数は、何重にもマクロ展開が走ってしまいます。
788 これはなにかおかしい気がしますね。Scheme などはどうなっているのか調べないと…。
789 </p>
790 ))
791 $(SECTION 微妙なところ3, $(SECBODY
792 <p>
793 これはエラーになります。
794 </p>
795 <pre>
796 >> let _ = (@macro twice(x) {x;x} in twice(print("Hello")))
797 polemy.failure.RuntimeException@C:\Develop\Projects\Polemy\polemy\value.d(109):
798 [<REPL>:2:35] 'twice' is not set in @value layer
799 </pre>
800 <p>
801 どういうことかというと、<tt>@macro</tt> で定義したマクロはいつから使えるようになるかという話で、
802 この <tt>@macro twice(x) {x;x} in ...</tt> の部分は <tt>@value</tt> レイヤの式なので、
803 まずこの式全体のマクロ展開が終わったあとにしか実行されないのです。<tt>twice</tt>
804 がマクロと見なされはじめるのは、<tt>@macro</tt> 実行が終わった後。
805 なので、
806 例えば <tt>twice(print("Hello"))</tt> の部分を無名関数にラップしてやれば、
807 マクロ展開を遅らせられて、 ちゃんと実行ができます。
808 </p>
809 <p>
810 これだと余りにも不便なので、関数のトップレベルの変数宣言式の列についてだけは、
811 <tt>@macro</tt> と <tt>@value</tt> の評価を交互にインターリーブするようにしました。
812 「関数やREPLのトップレベルの最初に宣言したマクロだけは、その関数内で即座に使える」わけです。
813 これも Scheme の let-syntax などなどの動きを調べて勉強しないと…。
814 </p>
815 ))
816 )
817 ))
818
819
820 $(SECTION Built-in Primitives, $(SECBODY
821 <p>
822 組み込み関数・変数の一覧。
823 </p>
824 $(DDOC_MEMBERS
825
826 $(SECTION テーブル操作, $(SECBODY
827 $(TABLE
828 $(TR $(TH {}) $(TD ()) $(TD 空のテーブルを作る))
829 $(TR $(TH .) $(TD (t, s)) $(TD テーブル t の名前 s のフィールドの値を取得。なければ <tt>undefined</tt>))
830 $(TR $(TH .?) $(TD (t, s)) $(TD テーブル t に名前 s のフィールドがあれば 1、なければ 0))
831 $(TR $(TH .=) $(TD (t, s, v)) $(TD テーブル t を親に持ち、名前 s のフィールドに v が入ったテーブルを作る))
832 )
833 ))
834 <br />
835
836 $(SECTION 制御フロー, $(SECBODY
837 $(TABLE
838 $(TR $(TH if) $(TD (n, ft, fe)) $(TD n が非 0 なら <tt>ft()</t>、0 なら <tt>fe()</tt> を実行))
839 )
840 ))
841 <br />
842
843 $(SECTION 演算, $(SECBODY
844 $(TABLE
845 $(TR $(TH +) $(TD (n, m)) $(TD 整数 n と整数 m を足して返す))
846 $(TR $(TH -) $(TD (n, m)) $(TD 整数の引き算))
847 $(TR $(TH *) $(TD (n, m)) $(TD 整数の掛け算))
848 $(TR $(TH /) $(TD (n, m)) $(TD 整数の割り算))
849 $(TR $(TH %) $(TD (n, m)) $(TD 整数の剰余))
850 $(TR $(TH &&) $(TD (n, m)) $(TD 整数 n と m が両方非 0 なら 1、それ以外では 0))
851 $(TR $(TH ||) $(TD (n, m)) $(TD 整数 n と m がどちらか非 0 なら 1、それ以外では 0))
852 $(TR $(TH ~) $(TD (a, b)) $(TD a と b を文字列化して結合))
853 $(TR $(TH <) $(TD (a, b)) $(TD a と b を比較))
854 $(TR $(TH <=) $(TD (a, b)) $(TD a と b を比較))
855 $(TR $(TH >) $(TD (a, b)) $(TD a と b を比較))
856 $(TR $(TH >=) $(TD (a, b)) $(TD a と b を比較))
857 $(TR $(TH ==) $(TD (a, b)) $(TD a と b を比較))
858 $(TR $(TH !=) $(TD (a, b)) $(TD a と b を比較))
859 )
860 <p>
861 注意点として、作者の趣味の問題で、<tt>&&</tt> と <tt>||</tt> は short-circuit 評価をしません。
862 整数演算の種類が少ないのは、D 言語の std.bigint がビット演算などをサポートしてないためです。
863 文字列が結合しかできないのは、単に手抜きです。
864 </p>
865 ))
866
867 $(SECTION 外部とのやりとり, $(SECBODY
868 $(TABLE
869 $(TR $(TH print) $(TD (a)) $(TD a を文字列化標準出力に改行付きで表示して、a を返す))
870 $(TR $(TH argv) $(TD ) $(TD スクリプトに渡された引数文字列のconsリスト))
871 $(TR $(TH gensym) $(TD ()) $(TD エセgensym。変数名として他とかぶらなそうな文字列を返します))
872 $(TR $(TH rand) $(TD (n)) $(TD 0 以上 n 未満の自然数を31bit以内でランダムに生成します))
873 )
874 ))
875 <br />
876
877 $(SECTION データ型判定, $(SECBODY
878 $(TABLE
879 $(TR $(TH _isint) $(TD (a)) $(TD a が整数なら 1、でなければ 0))
880 $(TR $(TH _isstr) $(TD (a)) $(TD a が文字列なら 1、でなければ 0))
881 $(TR $(TH _isfun) $(TD (a)) $(TD a が関数なら 1、でなければ 0))
882 $(TR $(TH _istbl) $(TD (a)) $(TD a がテーブルなら 1、でなければ 0))
883 $(TR $(TH _isbot) $(TD (a)) $(TD a が未定義値なら 1、でなければ 0))
884 )
885 ))
886 )
887 ))
888
889 )
890 Macros:
891 TITLE=Polemy Reference Manual
892 DOCFILENAME=index.html
893 SECTION=$(DDOC_DECL $(DDOC_PSYMBOL $1)) $(DDOC_DECL_DD $2)
894 SECBODY=$0