FSharpっぽい見た目でGoとして動く言語を作りたいなぁ、と思い、Folangと名付けて開発をしている。
karino2/folang: Funcitonal language transpiler to golang.
dotnetはやっぱりかったるさがあるので、runtimeやデプロイはGoが良いと思う。 でも言語はFSharpみたいなのが好きなので、なんかトランスパイルでどうにかならんかな? 実用にはならなくてもgoのお遊びとして結構やってみたい気もする。
とりあえず簡単なシンボルのツリーからgoのソース生成するのを作って、それを発展させていってそれっぽいものに出来ないかしら? セルフホスト出来る感じに出来たらちまちま時間をかけて進めていけそうな気もするが。
fsharpを移植したいのではなく、ランタイム的にはなるべくgoそのままにしたい。プラスアルファで型情報くらいは追加で持ってもいいかもしれないが。 という事で言語的には全く新しい言語になるだろう。
参考になりそうなリンクを貼っておく。
golang関連
golangによる言語処理系
参考になりそうな関数型言語系
その他
やった事を書く場所が欲しくてとりあえずここに置いておく。
Unionのgenericsを対応するにあたり、再帰型の扱いが難しくなってきて、lookupを必要になるまで遅らせるように直したくなる。 けれどいちいちlookupの辞書を全てに渡すのは嫌(大変更だから)なので、グローバル変数に対応しよう、と思い立つ。 これまでもGoの側で定義して関数でラップすれば使えたけれど、 別にグローバル変数に対応しても良いでしょう。
Golangのグローバル変数は、右辺が定数じゃないとconstは使えないので、全部varにする。
グローバル変数を使ってレコードやUnionの情報を辞書に入れて必要になるまでlookupを遅らせることでrecurive問題を解決し、 それをベースにUnionのgenerics対応をする。 なんとなく動いている風味か?
今後のタスクを考えたい。とりあえずcsvplrを移植したいなぁ、と思っているので、 パーサーコンビネータを作りたいと思っている。簡単な奴。 そのためにUnionのgenericsを実装したみたいな所もある。
そのためにも必要なことを列挙してく
この3つくらいかな。ビルドイン型はResult、Option、Dictあたりはビルドイン型にしたい。frtの型をプレフィクス無しで見えるようにするだけでいいとは思うんだが。
一方で今書いていて思ったが、パーサーコンビネータを作るだけなら上2つだけでいいな。もっと言えば一番上だけで良い。 ただparserはプレフィクスとしては長いので、そろそろopenは欲しいかもしれない。
とcsvplrのコードを見直してみたが、意外と面倒な機能をいろいろ使っているな。 次のターゲットにはあまり良くないかもしれない。 むしろ相互再帰のあるパーサーはparsecでは変数の副作用を使っているので、こういうのはgolangで書く方がいいのでは、という気もしてくる。
むしろ現状のセルフホストのパーサーをgenericなUnion使う版に書き直すか?もともとside effectがあまり無いスタイルのパーサーなので、 コンビネーター的なものになりつつあるので、もっと推し進めてもいいかもしれない。
一方でせっかくだから、いろいろな用途に使っていきたい、という問題もあるな。 csvplrは思ったより大変なのでもっと簡単な用途がいいかもしれん。
確定申告の息抜きにパーサーのコードを見直したり整理したりしていた。
ifとelifを並べたコードは、生成したコードが美しくないので、 matchのstringが欲しいなぁ、という気がする。 ただfolangとしては別にelifが並んでいるのはそんなに悪くないので、優先度は微妙。 実装もそんなに大変ではないはずだが。
あと、パーサーを、もっと共通で使える道具を増やしていこうとすると、以下のようなネストしたdestructuringに対応したいなぁ。
let (a, (b, c)) = ...
これがあれば、値を返すパースを2つつなげてtupleにする、という関数を作れば、 パーサーをつなげて書ける所が多い。
現状は、以下のコードは
let (a, b) = rhs
以下にトランスパイルされているが、
a, b := frt.Destr(rhs)
これが二段階になればいいのか?
a, _t0 := frt.Destr(rhs)
b, c := frt.Destr(_t0)
二行目以降の右辺は単なる変数になるのだから、再帰的にやっていけばそんなに大変ではなさそうではあるが。
csvplrが3要素タプルを使っていたのでサポートした そうしたら、これまで決めていた以下のルール、
[]T*U
は [](T*U)
T*[]U
もvalidの延長で、T*U*V
がT*(U*V)
になってしまって2要素タプルとみなされるように。
これは駄目だ。
やはり []T*U
は([]T)*U
とパースするしかないかぁ。
alecthomas/participle: A parser library for Go のexamplesのexpr4を見ていたら、手書きパーサーの例が出ている。 おぉ、いいじゃん、こういうのやりたかったんだよ、ということでparticipleでcsvplrの移植をやってみることにする。
デバッグ時に不便なので、Stringerを生成することにした。とりあえずUnionだけ。 Recordもそのうちやってもいいかもしれないが。
csvplrのパーサーをparticipleで書いてIRはUnionを生成するのができた。まぁまぁ簡単に出来たな。このくらいならF# とそんなに面倒さは変わらない気がする。 次はdataframe系のパッケージを使ってcsvのやりとりか。
csvplrの移植はなかなかいい感じのタスクな気がしてきた。
文字列のマッチとinner関数のletはそろそろ対応してもいいかもな。 後者は内部的にはfunのletに変換する感じにして単なるシンタックスシュガーとして扱う感じで。
stringのmatchをそろそろサポートしたい、と少しやってみたが、どうも既存のmatchのcasesの型がよろしくなく、 一緒に変更したらなんかいつまで経ってもコンパイル出来ない感じになってきたので一旦stashして型のリファクタリングから。 いい感じになる。
stringのパターンを増やすといかにも追加し忘れが出てきそうなので、exhausitve checkを実装することに。 割とあっさり実装出来て、これまで漏れてたのも見つかっていい感じ。
地味に変更が多くてやる気が出なかったstringのmatchをようやく実装。 その過程でトランスパイルのエラーメッセージを改善。だいぶエラーの場所をちゃんと教えてくれるようになってきた。
ついでにletのinner関数定義を対応。内部的には以下を
let localf a = someExpr
以下に変換している。
let localf = fun a -> someExpr
ちなみにこのaは、親の関数のtype parameterとして解釈されるため、このlocalfはこの関数内では一つの型としてしか使えない。(親の関数を呼ぶ時にお異なるtype argを与えて別の型にする事は出来る)。
これでcsvplrを移植するのに必要な機能は揃ったかな。
最近の変更分のドキュメントを更新しておく。まぁ読んでる人がどれだけいるかは微妙だが。
Folangの応用としてcsvplrを移植するのに、DataFrameのライブラリを選び、QFrameを使ってみる事にする。
tobgu/qframe: Immutable data frame for Go
この辺は特にこだわりは無いので使ってみて駄目ならほかを試す感じで。
見た感じ結構高機能なので、csvplrを移植するよりも、ASTから直接このQFrameのフィルタとかを生成する方がいいかもなぁ。