Rosetta Lisp in F#
.NET CoreによってLinux環境でも普通に .NET が動くようになってきたので触ってみた。F#はOCamlをベースにした言語なので、単にocalispを .NETのAPIを使うように移植していく形になるかと思ったが、実際書いてみると随分と毛色の違う実装になった。とはいえ、F#はOCaml同様に基本的な書き心地がよい言語に仕上がっている。
F#で何よりも注目されるのは .NET の資産の活用だろう。この点についてF#は非常にうまく適合している。OCamlで全然使われないことに定評のあるオブジェクト機能があったところに .NET object type? 互換のクラス定義やインターフェース定義がすんなりと収まっていて、ごく自然に .NET 標準のクラスライブラリを使用したりインターフェースの実装を行ったりが出来る。試していないがC#等の .NET の他の言語から利用できるクラスライブラリの提供も可能だろう。事前にF#で書かれたプロジェクトをいくつか眺めてみたが、インターフェースやクラスとメソッドによる実装も、モジュール上の関数による実装も活用されており、真にマルチパラダイムな言語と言える。fslispでも内部状態を持つ VM.fs などはクラスとして実装している。 F#には軽量構文というOCamlとは異なる構文が用意されている。これはHaskellにオフサイドルールを用いない明示的な構文が用意されているのとは逆のような関係で、オフサイドルールによってコードブロックが解釈されるため let の in を省略したり match ... with ... を begin .. end で囲ったりする必要がなかったりする。オフサイドルールは賛否両論あるが、特に後者の match ... with についてはOCamlの構文の中でも少々面倒くさいところなので素直に嬉しい:
code:nested_match.ml
match xs with
| x :: xs ->
(* begin .. end が無いとendの下のarmが内側のmatchの続きと解釈される *)
begin match xs with
| y :: xs -> ...
| _ -> ...
end
| _ -> ...
code:NestedMatch.fs
match xs with
| x :: xs ->
// インデントによって区別される
match xs with
| y :: xs -> ...
| _ -> ...
| _ -> ...
軽量構文以外でも書き心地を向上させる試みが為されていて、pipeline operator |> は非常に良い。OCamlに逆輸入されているけれどもocalispでは使っていなかったな... 他、アクティブパターンによる Sexpのリストへの分解 とか。 F#の興味深い言語機能の一つにコンピュテーション式があるが、今回は seq { .. } 等で触れるだけとなった。コンピュテーション式はfor記法やdo記法よりも踏み込んだ言語レベルのDSL構築という感じで、コンピュテーション式内のF#の各構文要素が Builder の対応するメソッドの呼び出しに変換される。コンピュテーション式内では Builder クラスに変換メソッドが実装されている範囲でF#の各構文要素を利用することができる。いわゆるバインドには yield! let! のように ! 付きの構文要素が用いられる。 要するにCPS変換をしているのでまずパフォーマンスが気になるが、 F# vNext は何が "ヤバい" のか: Monadic Programming の新時代 - Qiita に書かれてるような熱い話もある。 SRTPで型クラスっぽいこと もやっているが、正直SRTP周りの型推論の結果がだいぶよくわからない。その時点での文脈次第で型クラスでいう特定のインスタンスに特殊化された型が推論されたり...? 最後まで腑に落ちない感じだったが、これによってbuiltin関数の定義が型安全になったりしている。substrの例 では、 シグネチャの定義 ("substr", Str "str", Num "index", Num "bytesize", ()) に基づいて
->> の右辺に与える関数は VM -> (_ * string * float * float * unit) -> unit となっている。
S式のパースはHaskellのParsecのadaptationであるFParsecで。再帰部分が若干みすぼらしいが何ら問題なく書ける。 ところでどうにも他の処理系と比べて非常に遅いのは何が原因だろう... Rosetta Lispシリーズはもともと速度を度外視している処理系だけどもOCaml, Go, Scala実装と比べても一回り遅い。またOCamlのコンパイルが爆速なのと比較して妙にコンパイルに時間がかかるのもどうにも...これはSRTP駆使してるところとかで型推論器に無理をさせているかもしれない。