fly2 は限定継続をベースにしたスクリプト言語です。
オンライン版を使うとブラウザで試せます。
ocaml 4.04.0 と omake 0.10.2 をインストールして 以下のコマンドを実行します。
$ omake
$ ./fly2 src/hello.fly
hello, world.
js_of_ocaml 2.8.4 をインストールして以下のコマンドを実行します。
$ omake browser
$ js-of-ocaml fly2-browser
以下は画面に hello, world.
と表示して改行する Fly2 プログラムです。
(* hello.fly *)
writeln "hello, world.";
このプログラムを実行するには、上のプログラムを hello.fly
という名前で保存し、
シェルを立ち上げて、
$ fly2 hello.fly
と入力します。
実行結果:
hello, world.
プログラムの説明をします。
- 1行目はコメントです。OCamlスタイルのコメントが利用可能で、コメントの入れ子も可能です。
- 文字列を表示するには
write
関数かwriteln
関数を使います。write
は文字列を単に表示し、writeln
は文字列を表示した後で改行します。 - 文字列は内部では数値のリストとして表現されています。
- 文の最後にはセミコロンをつけます。
変数に値を代入するには、
! 変数名 = 式;
という文を記述します。
以下は、底辺がa高さがbの三角形の面積を求めるプログラムです。
!a = 5;
!b = 3;
!s = a * b / 2;
println s;
実行結果:
7.5
println
関数は任意の値を表示して改行する関数です。改行しないprint
関数もあります。write
とprint
の違いは、write
が文字列専用、print
は任意の値です。- 変数名はアルファベットの小文字から始める必要があります。
文字列を print
関数で表示すると、数値のリストが表示されます。
逆に、リストを write
関数に渡すと文字列が表示されます。
println "abc";
writeln [97, 98, 99];
実行結果:
[97, 98, 99]
abc
シフト式 ($ 変数 -> 式
) は、ブロック ({ 式 }
) で囲まれた範囲の継続を取り出して、
変数k
に束縛します。
!x = 1 + { 10 + $k -> k (k 100) };
println x;
実行結果:
121
継続を取り出して、単に捨てるだけの場合は、以下のように変数を省略できます。
!x = 1 + { 10 + $ -> 100 };
println x;
実行結果:
101
シフトとブロックにはそれぞれタグ (P
, Q
など大文字で始まる文字列) をつけることができます。
!x = 1 + {P: 10 + {Q: 100 + $P: k -> k (k 1000)} };
println x;
!y = 1 + {P: 10 + {Q: 100 + $Q: k -> k (k 1000)} };
println x;
実行結果:
1221
1211
- ブロックのタグを省略した場合は、無名のタグが指定されます。
- シフトのタグを省略した場合は、最も内側に挿入されているタグが指定されます。
関数を定義するには、シフトとブロックを使って以下のように書きます。
!double = { ($ k -> k) * 2 };
println (double (double 3));
実行結果:
12
($ k -> k)
は頻出のパターンなので、代わりに $$
と書くことができます。
!mult = { $$ * 2 };
println (double (double 3));
実行結果:
12
複数の引数をとる関数は、複数 $$
と書くことで定義できます。
!mult = { $$ * $$ };
println (mult 10 20);
実行結果:
200
引数を複数使う場合は、以下のように書きます。
!pow2 = { !x = $$; x * x };
println (pow2 12);
実行結果:
144
条件分岐をするには、
? 式;
という文を使います。?
文は、式が真の場合は式の値を返し、偽の場合は、($ -> v)
(v は式の評価結果)を実行します 。
!x = 10;
println { ? x > 0; 123 };
println { ? x > 10; 456 };
実行結果:
123
0
条件が偽の場合のタグを指定するには、
?P: 式;
のように記述します。
パターンマッチをするには、
! パターン = 式;
という文を使います。!
文は式を評価してパターンとマッチさせます。パターンマッチに失敗した場合は、($ -> v)
(v は式の評価結果)を実行します。
!x = [1, 2, 3];
println { ! [] = x; 123 };
println { ! x :: xs = x; xs };
実行結果:
[1, 2, 3]
[2, 3]
パターンマッチに失敗した場合のタグを指定するには、
!P: パターン = 式;
のように記述します。
これまで出てきた構文を使うことで、不動点演算子を以下のように定義できます。
!fix = { !f = $$; { !x = $$; f { !y = $$; (x x) y } }
{ !x = $$; f { !y = $$; (x x) y } } };
この不動点演算子を使うことで、階乗を計算する関数は以下のように記述できます。
!fact = fix { !f = $$; !n = $$;
{R: {!0 = n; $R: -> 1} * f (n - 1)}
};
println (fact 5);
不動点演算子は@
演算子として標準ライブラリで定義されているので、毎回自分で定義する必要はありません。
!fact = @ { !f = $$; !n = $$;
{R: {!0 = n; $R: -> 1} * f (n - 1)}
};
println (fact 5);
実行結果:
120
以下はn
番目のフィボナッチ数を求める関数の定義例です。
!fib = @ { !fib = $$; !n = $$;
{IF:
{? n = 0; $IF: -> 1};
{? n = 1; $IF: -> 1};
fib (n - 1) + fib (n - 2)
}
};
println (fib 10);
実行結果:
89
リストのマップ関数の定義例です。
!map = @ { !map = $$; !f = $$; !lis = $$;
{CASE:
{ ! [] = lis; $CASE: -> []};
{ ! hd :: tl = lis; f hd :: map f tl }
}
};
println (map {$$*2} [1,2,3]);
実行結果:
[2, 4, 6]
浅井先生と亀山先生の論文に載っている例。
!prefix = { !lst = $$;
!visit = @ { !visit = $$; !lst = $$;
{CASE:
{! [] = lst; $P: -> []};
{! hd :: tl = lst;
hd :: ($P: k -> k [] :: {P: k (visit tl)})}
}
};
{P: visit lst};
};
println (prefix [1,2,3,4,5]);
実行結果:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
$ fly2 src.fly
ソースファイル src.fly
を実行します。
fly2 のプログラムはセミコロンで区切った文の列で表します。
文1;
文2;
...
文n;
最後の文n
のセミコロンは省略できます。文には以下の3種類があります。
- 式文
!
文 (パターンマッチ文)?
文 (条件判定文)
(* コメント *)
(*
と *)
で囲まれた範囲はコメントとして無視されます。コメントは入れ子にできます。
式;
式を実行して結果を現在の値に設定します。
! パターン = 式;
! タグ : パターン = 式;
式を実行してパターンとマッチさせます。 マッチした変数は以降の文で使うことができます。
パターンマッチに成功した場合、式の値(v
)を現在の値に設定します。
パターンマッチに失敗した場合、($ -> v)
もしくは ($ タグ : -> v)
を実行します。
パターンには以下のものがあります。
- ワイルドカードパターン(
_
) - 変数パターン
- 数値リテラル
- orパターン (
p1 | p2
) - asパターン (
p & x
) - リストパターン (
[p1, p2, ...]
) - コンスパターン (
p1 :: p2
)
? 式;
? タグ : 式;
式を実行して値をv
とすると、v
が0
またはv
が[]
の場合、($ -> v)
もしくは ($ タグ : -> v)
を実行します。
v
が0
以外かつv
が[]
以外の場合、v
を現在の値に設定します。
数値は内部では倍精度の浮動小数点で表現されています。 数値リテラルとして、以下のような表記が可能です。
0
12
3.14
5e-3
リストは値の列です。 リストリテラルは
[]
[1, 2, 3]
のように表記します。
文字列は内部では数値のリストです。 文字列リテラルは、
"abc"
のように表記します。上のリストは、
[97, 98, 99]
と同じです。
エスケープシーケンスとして、
文字 | 意味 |
---|---|
\n |
改行 |
\t |
タブ |
\r |
復帰 |
\\ |
\ |
\" |
" |
が利用できます。
演算子には以下のものがあります。
演算子 | 機能 |
---|---|
-e |
単項マイナス |
@e |
e の不動点 |
e + e |
加算 |
e - e |
減算 |
e * e |
乗算 |
e / e |
除算 |
e < e |
より小さい |
e > e |
より大きい |
e <= e |
以下 |
e >= e |
以上 |
e = e |
等しい |
e <> e |
等しくない |
e :: e |
リストのコンス |
シフト式には以下の形式があります。
$ -> 式
$ タグ: -> 式
$ 変数 -> 式
$ タグ: 変数 -> 式
シフトはブロックで囲まれた間の継続を取り出します。 タグを省略すると、現在の位置からもっとも内側のリセットが指定されたことになります。
ブロック文には以下の形式があります。
{ 文1; 文2; ... }
{タグ: 文1; 文2; .. }
ブロックはタグを文脈に挿入して、文を順番に実行していきます。 ブロック自体は最後の文の値を返します。 タグを省略すると、無名のタグが指定されたことになります。
関数名 | 機能 |
---|---|
print e |
任意の値を表示する |
println e |
任意の値を表示して改行する |
write e |
文字列を表示する |
writeln e |
文字列を表示して改行する |
true |
値1 |
false |
値0 |
not e |
e が真(0 でないまたは[] でない)の場合1 を返し、それ以外の場合0 を返す |
is_list e |
引数がリストの場合1 それ以外の場合0 |
is_num e |
引数が数値の場合1 それ以外の場合0 |
to_str e |
値を文字列に変換 |
floor e |
数値を整数に切り捨て |