概要
本書はOCamlのベテランプログラマ2名による解説本です. 第2版が2021年にかかれていて、ネットで無料公開されています.
全3章は以下のような構成で, OCaml初心者でも読めますし, 深堀りされた解説からは経験者でも得るものがありそうです.
- OCamlの機能一通り(基礎文法, ヴァリアント, レコード, ファンクター, GADT, Classなど)
- 具体例(コマンドラインの引数パーズ, 非同期通信, jsonパーザ)
- OCamlのランタイムとコンパイラの仕組み
個人的にここ数か月OCamlに興味を持って少しずつ触れてきたのですが, 入門の次に進むための知識が得たいと思い本書を読みました. 知りたかったことは例えば以下のような点です.
- 標準ライブラリにはそれほど機能が揃っていないが, 便利なライブラリがあるのか(または自分でライブラリを整備するのが普通なのか)
- プロジェクトをどのようにファイル構成するか
- どの程度の規模でモジュールを分けるか
- テストはどうやって書くか
こういった事柄はやや抽象的でそのものズバリ検索するのが難しいと思っていますが, 本書には知りたかったことは全て書いてありました. そのくらい網羅的で, もちろん知らなかったこと(知りたいとすら思わなかったこと)も満載でした.
高度な機能や具体的なライブラリの使い方などは読んでもピンとこなかったり軽く読み飛ばしたりした箇所があるのですが, 今後必要になったときに改めて読み返そうと思うような内容でした.
BaseとJane Street
本書では一貫してBaseというライブラリが使われています. BaseはOCamlの標準ライブラリを置き換えるべく作られたライブラリで, 今やデファクトスタンダードとなっている(らしい. 少なくとも私はそのような印象を受けた)OSSです.
メインの開発元はJane Streetという企業です. OCaml界隈では存在感のある会社で, 株やオプションなどの取引市場を提供する金融×Techの会社です. 複雑な金融商品を取り扱うためにテクノロジーに注力していて, その競争力の源泉となっているのがOCamlだということのようです. おそらくOCamlを使っている会社としては最も有名だと思います.
標準ではないライブラリに常に依存するのは若干抵抗がありますが, 非常に便利なので使えるなら使ったほうが良いと思います.
とはいえ, 実際にどの程度使われているのかはちゃんと調べていません. 歴戦のOCamlプログラマーは自作のライブラリを持っていると思うので, 不要だったりするのかもしれません.
すべてがリストだとこんなにも便利
本書で得た知識の中でも, 知っているのといないのとでは大違いだと思ったものにパイプ演算子|>
があります. 演算子の定義は以下の通りです.
let (|>) x f = f x
引数 関数
の順番を入れ替えて関数 引数
とする演算子です. つまり, 以下の2つが同じになります.
let m1 = List.map [1; 2; 3] ~f:(fun x -> x * 2) (* [2; 4; 6] *)
let m2 = [1; 2; 3] |> List.map ~f:(fun x -> x * 2)
これだけだと一体なんの役に立つのかさっぱりですが, 例を見ると納得です. 0-99の中から, 13の倍数を抽出し, 7で割ったあまりが大きい順に表示する例です(この例自体には特に意味はありません).
open Base
List.range 0 100
|> List.filter ~f:(fun x -> x % 13 = 0)
|> List.map ~f:(fun x -> x % 7)
|> List.sort ~compare:Int.compare
|> List.rev (* = reverse *)
この例のように, リストに対する処理をパイプで連鎖させることができます. ネストが深くならないので見やすいですし, あとから行やコメントを追加することも容易いです.
もし同じことをパイプ演算子なしでやると, 以下のようになります. あまりにも見辛いです.
List.rev
(List.sort ~compare:Int.compare
(List.map
~f:(fun x -> x % 7)
(List.filter ~f:(fun x -> x % 13 = 0) (List.range 0 100))))
このパイプ演算子はUnixのパイプ(テキストを連鎖させる)やC#のLinq(IEnumerableを連鎖させる)と似ていると感じます. データ構造を統一するとこういう利点があると気付かされました.
OCamlのエコシステム
duneというビルドシステム(RustのCargoのようなもの. C/C++のmakeの強化版), opamというパッケージマネージャ(gem, pip, npmのようなもの)があります.
モダンな言語には標準装備されているような気がしますが, やはりあると安心です.
ちなみにduneの設定ファイルにはS式を使います. さらに, デバッグなどのシリアライズ用にもS式を使います.
jsonやXMLではなくてS式を使うのは関数型っぽくて良いと思います. S式は関数型世界の共通言語なのでしょうか.
# Int.sexp_of_t 5;; (* int -> Sexp *)
- : Sexp.t = 5
# Sexp.to_string (List.sexp_of_t Int.sexp_of_t [1; 2; 3]);; (* List -> Sexp -> string *)
- : string = "(1 2 3)"
凄そうだけどピンとこない機能
モジュール(Module)という便利な機能があります. これは, ある程度のコードをひとまとめにしてインターフェイスを定義できるもので, C++だとClassのようなものです.
ファンクター(Functor)という, モジュールを引数にとってモジュールを返す関数のような機能があります. ごく簡単な例として, モジュールが持つ変数xに1を足したモジュールを返すファンクターが紹介されています.
module Increment (M : X_int) : X_int = struct
let x = M.x + 1
end
module Three = struct
let x = 3
end
module Four = Incremnt(Three) (* Four.x = 4 *)
これは, 私にとって馴染みあるC++などの言語には相当するものがない機能だと思います. 強いて言うなら継承が近いでしょうか.
その他に, 端点を表すモジュールを引数にとって区間を扱うモジュールを返す例が紹介されています. これは確かに凄いですが, しかし他の使いみちをパッと思い浮かびません.
今まで触れたことがない概念であり, 抽象度も一段高いので理解しづらいのかなと思います. 読むだけだとピンとこないので, 実際にコードを色々書いてみて必要になったときに初めて理解できそうな気がします.
その他に, エラーハンドリングやCore.Asyncの非同期処理など, 読んですぐにはピンとこないものの必要になったときに再度参照したい項目がありました.
実践ワークショップ
本書とは別のものですが, Jane Streetが提供しているlearn-ocaml-workshopというリポジトリがあります. OCamlの基礎文法を一通りテストできる演習問題がまとまっていておすすめです.
練習の一環としてスネークゲームを実装する課題がありました. 穴埋め形式でコードを書いてテストを通せば良いので取り組みやすかったのですが, ちゃんと分かった気がしなかったので, 改めて自作しました.
なるべく依存が少なくなるようにして, 端末で動くシンプルなものを作りました. 実装にあたってcurses/ncursesのOCamlバインディングcursesを使わせていただきました.
スネークゲームのロジック自体はそこまで複雑ではありませんが, Core.Unixから低レベルなAPIを使ったnon-blockingキー入力や, Cursesで未実装だったcbreak, noechoの実装が個人的な見どころです.
結び: OCamlの何に魅力を感じるか
正直なところ本書の内容を理解しきれておらず, 紹介できていない内容が多数あります(GADTなど...). これらは今後OCamlを使い続けていくうちに, いつか分かるときが来ると思っています. 再読して理解が深まったら加筆するかもしれません.
改めて, 何が面白くてOCamlに触れているのか考えてみると, C++にはない概念や機能があるということが大きいと思います.
- ファンクター, ヴァリアントなどはC++にはない
- 基本的にすべてimmutable(C++でもどうせほとんどの変数にはconstをつけるので, デフォルトがimmutableな方が好き)
- Listが主要なデータ構造で, Listを使った処理が上手く書ける
- 強力なパターンマッチ
- モジュールに型を定義して, 型の実装を非公開にしてインターフェイスを定義する(Sexp.tのように, 型の中身は分からない)ような流儀
今後OCamlが流行るのか廃れるのかは分かりませんが, 少なくとも新しい見方や考え方を得られるのは, 損得抜きに純粋に楽しいです.
どれくらいOCamlに深入りするかは決めてませんが, 次はスネークゲームよりは大きいプログラムを作ろうと思っています.