Posted at

OCaml初心者によるOCaml雑感

11月くらいから、とある理由でOCamlをそこそこ本格的に(?)使い始めることになりました。とはいえ、私が書いた分のコード、まだ1000行にも満たないのですが。ともあれ、1ヶ月くらい、ちょくちょく触ってみての雑感を書いてみたいと思います。


構文編


リストのセパレータがセミコロンである

これは、よくハマる部分の筆頭です。OCamlでは、リストの要素を区切るのに、多くの言語で採用されているカンマではなくセミコロンを採用しています。たとえば、1,2,3,4からなるリストは

[1, 2, 3, 4]

ではなく、

[1; 2; 3; 4]

と記述します。リストや配列のセパレータに何を採用するかというのは些細な問題ではあるのですが、前者のコードがOCamlでは構文エラーにならないことが若干ややこしいです。端的にいうと、前者の型は

int * int * int * int

という4つのintからなるタプルのリスト((int * int * int * int) list)になりますが、後者はintのリスト(list int)になります。これが型推論と組み合わさったときに、わかりにくいエラーメッセージの原因になり、時間を浪費する原因になることが多々ありました。


データ構築子の引数はタプルである

これは型的な話でもあるのですが、たとえばHaskellと違い、通常、データ構築子は

Foo (1, 2, 3)

のように括弧で囲んで、というか、タプルを与えるわけですが、なんとなくHaskell的な気分で

Foo 1 2 3

と書いてしまい、罠にはまることがしばしばありました。これは、Haskell的な感覚で書こうとする方が悪いのですが。


let の後に必ず in が必要

ぶっちゃけ慣れの問題なのですが、

let x = ... in

...

とすべきところ、しばしば

let x = ...

...

としてしまい、構文エラーになることがときどきあります。inが必要なことが悪いわけじゃないのですけど、なんとなくうまくinを補ってくれる構文だったらより嬉しいですね。


パイプライン演算子は便利

これはいい面ですが、map みたいな高階関数をつなげて書くときに、

List.map ~f:(fun x -> x * 2) (List.map ~f:(fun x -> x + 1) lst)

と書く必要はなく、

lst |> List.map ~f:(fun x -> x + 1) |> List.map ~f:(fun x -> x * 2);;

と書けるのは良いですね。左から右に書けるのが楽ですし、読むときも楽です。他にも、似たようなパイプライン的演算子はいくつかあるのですが、そういうのが(base 込みでなら)あるのは良い感じです。


ラベル付き引数が便利

OCamlのラベル付き引数は、他言語の名前付き引数に相当するものですが、ラベル名と仮引数名を別にできたり、部分適用できたりするのが便利です。特に、高階関数では引数のうちどれがどれだっけ問題がしばしば発生するので、

lst |> List.map ~f:(fun x -> x + 1) |> List.map ~f:(fun x -> x * 2);;

こういうふうにラベルfを指定して関数を渡せるようになっているのは楽です。


型システム編

OCamlの型システムについてそれほどよく知っているわけではないので、あまり書けることはないのですが、ひとつ言えるのは型推論が強いことは便利だけど、一方で、慣れないとエラーメッセージを解読するのが大変、というあたりでしょうか。OCamlが主要型を持っているのか、それを完全に推論できるのかについてはちゃんと知らないですが、普通に書いていれば、主要型に近い型(?)をうまく推論してくれる感じなので、この辺はやはり型推論が弱い(Scala等も含む)言語に比べて便利です。Scalaで書くときは、必要であるというのもありますが、まず最初にシグニチャを書いてそれから本体を書く、というスタイルでしたが、OCaml書いているときは、とりあえず型シグニチャは書かずに始めることが多い気がします。mliに書くシグニチャとかも手動だと面倒なので、適当におかしな型を書いて、型エラーをあえて発生さえて、推論された型をmliに書いたりしています(この辺って手動ではなく、ツールでmli自動生成してくれるものはあるんでしょうか?)

型推論については、30行くらいある、型制約を書かないコード書いたときの型エラーのメッセージがまだうまく読み取れなくて、必要に応じて型制約を追加してエラーの原因を探したりしています。慣れてくると自然にわかるようになるんでしょうか?


ツール編

この辺は、ここ数年で大きく変わったようなのですが、今はdune(ビルドツール)とopam(パッケージ管理ツール)の組み合わせがスタンダードらしい(?)のですが、duneとopamを組み合わせて使うときに使い方がわかりにくいです。この辺は、sbtみたいに単一のビルド定義ファイルに依存関係もビルドの設定も書ける方が便利だなと思います。あとは、最近のパッケージマネージャでありがちな、scaffoldを自動生成してくれる機能が欲しいです(サードパーティのだとある?)

ビルドが早い(ScalaやHaskellと比較して)のはいいところだなと思います。


まとめ

なんか書いてみると、実はまだそこまで不満に思っている箇所が多くないなということに気づきました。duneとopamについてまだよくわかっていないですが、言語としてはまあ普通に慣れていけそうな気がしました。新しい言語で継続的にコードを書く経験は久しぶりなので、楽しんでいます。