Fulynとは、ズバリOSECPU用の高級言語、そして関数型言語だ。
最新版の開発版バイナリは
https://github.com/lambdalice/OSETools/tree/develop/binary
からどうぞ。
Fulynを使うことにより、効率的なコーディングが可能になる(かもしれない)。
とりあえず、ここを見よ! => Fulyn_Samples
Fulynは関数と文と式でできている。
関数は、プログラムの一番表層に置かれる。
関数は、実行することができる。
文には、宣言と代入がある。
宣言文では、変数を宣言することができる。
代入文では、変数に式の評価結果を代入することができる。
式には、リテラル、変数、パターンマッチの三種類がある。
リテラルには、数字とラムダがある。
変数には、式の評価結果を保存できる。
パターンマッチで、分岐ができる。
プログラムの基本はHello, World!だ。
しかし、Fulynの配列はちょっとめんどくさいので、とりあえず一文字だけ表示してみよう。
main putchar('a') end
まさに単純明快!Fulynはmain関数から実行される、これが重要。
Fulynは関数型言語なので、すべてが関数だ。そして、Fulynはすべてが宣言式と代入式で構成される。
putchar('a') は何にも代入していないように見えるが、それは後に説明する。
Fulynは高級言語だから、変数の定義もできる。
main x :: int x = 0 putnum(x) end
これで、int型の変数xが宣言され、xに0が代入され、0と表示された。もちろん、ASKAみたいにレジスタの定義なんてめんどくさいことは必要ない。
しかも、ものぐさなあなたのために、関数内で変数を宣言するときは、型推論機能により宣言を省略することができる。つまり、このように書ける。
main x = 0 putnum(x) end
型が「0」というリテラルから自動で推論され、「x :: int」を書いたことにしてくれるのだ。
ただし、Fulynではグローバルな変数の初期化ができない。そのため、グローバルでは、
x :: int
としか書くことができない。代入は関数内からでしか不可能なのだ。
そして、Fulynは条件分岐が可能だ。条件分岐も式で、結果を返す。
条件分岐はこう書く。
main x = 0 y = 1 ? gra(x, y) | true -> putnum(0) | false -> putnum(1) end
gra関数は、greater thanの略で、>と同じだ。
そう、1が表示される。
また、switch-caseのように使うことも可能だ。
main x = rand() y = ? mod(x, 3) | 0 => 0xa | 1 => 0xb | () => 0 end
これは、マッチする値 -> 返す式と書く。
Otherwiseは()と表す。
最後に、Fulynは四則演算も可能だ。
main putnum(sub(mul(add(1, 2), 3), 4)) end
もちろん、5が表示される。今のところ演算子は実装されていない。ごめん。
関数型言語なんだから関数が書けないとまずい。さっそく書いてみよう。
でもその前に、「関数型」の表し方について解説しておこう。
Fulynは関数型言語なので、関数自体をファーストクラスに扱うことができる。
要するに、関数と関数をとって関数を返す関数もつくれるわけだ(頭がこんがらがるかもしれないけど、ついてきてほしい)。
「関数型」は、このように記述する。
func :: [int => int => int]
これは、Cで書くと
int func(int,int);
と同じ意味になり、型宣言だが、働き上はプロトタイプ宣言と同じだ。
Haskellをやっている人ならわかりやすいだろう。引数の型を=>でつなぎ、最後に返す型を=>でつないで書く。
関数型だけは、グローバルであっても初期化が許されている。その代り、immutable(不変)となり、再代入ができない。(定義した関数を書き換えられたらとんでもないからね)
初期化をしなかった場合は、再代入が自由なただの変数となる。
では、関数を書いてみよう。Fulynでは関数の記述に二通りのシンタックスを用意している。
ひとつはλ(ラムダ)記法。これは簡単だ。
func :: [int => int => int] func = x => y => add(x, y)
もちろん、受け取った二つの値を足して返す関数だ。数式に近いのでわかりやすいだろう。
ラムダは、引数を=>でつないで、=>の後に返す式を書く。ちょうど型の記述と同じ形になっている。
もうひとつは、Statement(ステートメント)記法。これが難しい。
Statement記法で関数を書くときにも、まずプロトタイプ宣言をする。
func :: [int => int => int]
そして、次のように書く。
func(x,y) _ = add(x, y) end
_ というのは特殊な変数で、"returner"と呼ばれる。returnerは、関数から抜けるときに格納している値を呼び出し元に戻す。関数の開始時には、初期値(intなら0、funcなら() => 0)で初期化されている。
変数に入れる必要のない値を_に入れることで葬る事ができる。returnerに値を代入する操作を「捨てる」ともいう。そのため、_ は"trash"でもある。捨てる動作は、戻り値の型にかかわらず行えるが、型が違う場合は無視される。
また、returnerの記述は省略することができる。つまり、このように書ける。
func(x,y) add(x, y) end
これが、前の「putchar('a')」の文の正体だ。
さらに、Fulynでは $ 、その名を"breaker"という特殊な変数を用意している。
breakerは、参照先はreturnerと同じだが、評価された瞬間に関数を終了する。C言語などで言うreturnの操作が、ちょうどbreakerに代入する操作にあたる。
func(x,y) $ = add(x, y) 42 end
と書いた時、42は評価されない。
Statement記法では、複文関数が組めるだけでなく、returnerとbreakerで複雑な流れ制御を実現できる。
Fulynでは配列を扱うことができる。
しかし、Fulynには配列型がない。
そう、配列ですら関数なのだ。美しいじゃないか。
配列を作るには、array_new関数を使う。
(組み込み関数の一覧はそのうち作るよ)
arr = array_new(3)
これで、長さが3の配列を確保できた。
次に、値を代入してみよう。
array_set(arr, 1, 42)
これで、インデックス1に42が入った。
最後に、値を取得してみよう。
putnum(arr(1))
もちろん、42が表示される。
この魔法のような仕組みは川合さんのおかげです。本当にありがとうございます。くれぐれも足を向けて寝ないように。
でも現状ちょっと不安定なので、関数呼び出しの中で呼ぶとたまにセキュリティ例外吐くかも。そういう時は、一回ローカル変数に代入してから使ってね。
Fulynでは関数がファーストクラスなので、いわゆるクロージャを書くこともできる。
クロージャを書くときは、型の宣言をしないといけないので、
main func = x :: int => y :: int => add(x, y) func(3, 4) end
というように、引数の後に::で型を指定してあげる必要がある。
クロージャはローカル変数を利用できるし、普通の関数と同じように呼び出したり、他の関数に渡したり、いろいろできる。
Fulynでは、明示的宣言により末尾再帰の最適化ができる。
関数の頭に、
main : tailcall main() end
というようにtailcallキーワードをつけてあげれば、スタックを食いつぶさない無限再帰となる。
ただし、呼び出し元には二度と戻ってこない。注意。
コメント | お名前 | NameLink | |