Skip to main content

Command Palette

Search for a command to run...

『Real World OCaml』で入門レベルを脱する

Updated
『Real World OCaml』で入門レベルを脱する

概要

本書はOCamlのベテランプログラマ2名による解説本です. 第2版が2021年にかかれていて、ネットで無料公開されています.
全3章は以下のような構成で, OCaml初心者でも読めますし, 深堀りされた解説からは経験者でも得るものがありそうです.

  1. OCamlの機能一通り(基礎文法, ヴァリアント, レコード, ファンクター, GADT, Classなど)
  2. 具体例(コマンドラインの引数パーズ, 非同期通信, jsonパーザ)
  3. 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に深入りするかは決めてませんが, 次はスネークゲームよりは大きいプログラムを作ろうと思っています.

More from this blog

キャリア 6 年目の振り返り

2020 年 4 月に働き始めてから 6 年近く経過した。今年を振り返ってまとめておく。 正社員化 2025 年前半あたりは、生活に安定感がなくジャグリングをしているような感覚だった。学校の卒業、ビザの切り替え、インターンから正社員への移行という3つのイベントを同時に進めていた。結果的に 5 月から正社員として働き始めたが、計画通りには行かなかった。 会社の人事プロセスが大幅に遅延した。本来は 4 月から働き始める予定だったのに、一ヶ月以上遅れて 5 月になってしまった。これに関しては、本当にこ...

Dec 29, 2025
キャリア 6 年目の振り返り

キャリア 5 年目の振り返り

2020 年 4 月に働き始めてから 5 年が経った。本当は毎年 12 月に一年の振り返りをしようと思っていたのだが、今回はいろいろあって 4 月になってしまったので、2024 年 1 月から 2025 年の今までを対象として振り返りをして、今年の目標を書こうと思う。 海外就職 カナダに渡航して約 1 年半経った。最初の一年間は private college という日本の専門学校のようなところに通っていて、それからジョブオファーを得て働き始めた。 海外で働いてキャリアを積むというのがカナダに来...

Apr 14, 2025
キャリア 5 年目の振り返り

ReRe: Recursive Redefinition

38 posts