- OCamlの言語的特性とエコシステムは優れており、個人・プロフェッショナルの両プロジェクトに適している
- 静的型システム、代数的データ型、モジュールシステム、オブジェクトモデル、ユーザー定義エフェクトなど、多重パラダイムと高度な機能が安定して統合されている
- OPAMパッケージマネージャー、Duneビルドシステム、LSP/Merlinエディタ支援、Odocドキュメント化ツールなどの成熟したツールチェーンが整っており、Web・ブロックチェーン・ツーリングなど多様なライブラリエコシステムを備えている
- コミュニティは アクセスしやすさ・親切さ・専門性 を備えており、学習と協業がしやすく、継続的な進化によって将来性も明るい
OCamlを主要言語に選んだ理由
- 筆者は長年にわたりさまざまなプログラミング言語を使ってきたが、その中でも OCaml を主要言語に選んだ
- OCamlの最大の長所として、強力な静的型システム と、Cや他の関数型言語と比べても優れた 関数型プログラミング支援 を挙げている
- その型システムのおかげで、多くの バグ防止 とコード最適化を経験してきた
- 実際に複数の 開発プロジェクト でOCamlを活用し、生産性 と 安定性 が大幅に向上した経験がある
OCamlの長所と実務での活用
- ほとんどのコードを素早く書け、関数の合成 と 不変データ の利用によって安全性が高まる
- 近年はOCamlの エコシステム と ツール(IDE、ビルドシステムなど) も継続的に発展している
- 多様なライブラリと外部パッケージにより、実務での 効率的な開発 が可能になる
- PythonやJavaと比べるとOCamlはそれほど有名ではないが、生産性・安全性・柔軟性 の面で非常に強力な選択肢である
言語的特性
- 研究由来と産業応用が結びつき、表現力・安全性 を重視した機能発展が進んできた
- ユーザー定義エフェクト、affineセッションなどの最新機能
- 静的型検査 はセーフティネットであると同時に設計ツールでもあり、貧弱な型体験から来る誤解を払拭する
- 多重パラダイム: 関数型、命令型、モジュール型、オブジェクト指向、マルチコア対応
- ML系の構文 は簡潔で一貫しており、ReasonMLのような代替構文も存在する
- 代数的データ型(積・和・指数型)とパターンマッチング、多相性により、データ/ドメインモデリングに強みを持つ
- モジュールシステム はインターフェースと実装の分離、抽象化、再利用、高度な多相性まで支援する
- 依存性逆転: モジュール/エフェクトを通じた柔軟な注入方式を提供する
エコシステムとツーリング
- コンパイルターゲット: ネイティブ、バイトコード、JavaScript(
Js_of_ocaml, Melange)、WebAssembly
- MirageOS によるマルチコンテキストライブラリ記述の規律
- OCaml Platform:
- OPAM: バージョン管理・スイッチ・パッケージインデックス、CI支援
- Dune: 高速ビルド、S式設定、
dune-release による配布の簡素化
- LSP/Merlin: VSCode、Emacsなどでのコード補完・ナビゲーション・フォーマット
- Odoc: 相互参照・マニュアルページ・doctestなどを支援
- 豊富なライブラリ: Web(Dream, Ocsigen)、ブロックチェーン・暗号(HACL*)、テスト(alcotest, qcheckなど)
- 標準ライブラリは小さいが、Batteries、Base/Core、Containersなどの代替が存在する
新たな挑戦とコミュニティ
- OCamlコミュニティは小規模ながら 継続的に成長 しており、ユーザーフレンドリーな流れを見せている
- 新しい言語やパラダイムへの挑戦を望む開発者にとって、OCaml は深く学ぶ価値のある言語である
- 多くの利用者は、OCamlの利用経験を通じて 新しい視点 と 問題解決力 が高まると述べている
結論
- OCamlは特定分野(例: 金融、コンパイラ、システム開発)に限られず、汎用的に活用できる 強力なプログラミング言語 である
- 実践で得られる効率性、保守性、問題防止能力 などは、実際の業務現場でその価値を証明している
- 最新の言語やトレンドと比べてやや知名度が低いとしても、信頼性と安全性を重視するなら十分に 検討に値する選択肢 である
2件のコメント
かつて大学院でOCamlを扱ったことはあるが、エコシステムが本当に乏しく、リファレンスもあまりなく、とくに質問できる相手がいない。個人的な基準では、韓国ではプログラミング言語学会の界隈でもなければ使っている例はほとんどないはず。COBOLのようなものは聞いたことがあっても、OCamlは聞いたことがないだろう。
Hacker Newsの意見
GoogleでRustをAndroidチームに導入した経験についての発表を見たことがある。その中で2つ印象的だった点がある。さまざまなプロジェクトをPythonからRustへ移したので、性能はそこまで大きな問題ではなかったのだろうということと、Rustユーザーが最も気に入っていた機能はパターンマッチングやADT(Algebraic Data Types)のような基本的なものだったということだ。だからRustが本当に大きく貢献した部分は、ライフタイムのような固有機能よりも、1990年代のML言語がすでに提供していた要素だったように感じる。もしOCamlが2010年ごろにマルチコアのような不便さを解決していたら、Rustと同じくらい人気が出ていたかもしれない。残念ながらOCamlは学界と産業界の間の溝にはまってしまった。ひとつ付け加えるなら、31ビット整数はビット演算では実用上かなり不便で、見た目の面では二重セミコロンが本当に好きになれなかった
OCamlは当時すでにかなり良い状態だったと思う。2010年には仕事でPythonよりずっと快適に使っていた。JaneStreetが成し遂げたことを見れば十分だ。OCamlが広く採用されなかった最大の理由は、アメリカで作られた、あるいは主導された言語ではなかったからだと思う。言語の人気が技術的な優秀さによるものだと信じたくなるが、結局は流行の問題だ。Rustが大衆的に成功した理由も、大規模な宣伝と積極的なコミュニティ活動のおかげだ。専任スタッフまで置いて積極的に名前を広めていた
Googleは実際のサービスコードに使える公式言語のリストをできるだけ短く保とうとしている。Rustが選ばれたのは、C++を置き換えたり補完したりできる言語だからだろう。OCamlはその位置に立ちにくかった(Goなら置き換えられたかもしれないが可能性は低い)。だからRustが選ばれた最大の理由は、ADTを提供する公式言語の中で唯一だったからであって、ビルド速度を重視しなかったからではないはずだ。OCamlがRustの代わりになれなかったのも当然だ。GCのある言語はすでにGoやHaskellなどいろいろあり、2010年ごろにベアメタルを狙えるほど表現力の高い言語はC++しかなかった(それもC++11やC++17以前はもっと厳しかった)
完全に同意する。OCamlがいくつかの細かい問題を解決していれば、本当に重要なプレイヤーになれたはずだ。ビルド速度は今でもRustよりずっと速い。だがOPAM(パッケージマネージャー)はよくバグが出るし、分かりにくいことで有名だ。Windows対応は非常に深刻なレベルで悪い。昔のPerlのWindows対応よりひどい。公式ドキュメントは簡潔すぎて、役に立たないレベルだ。文法自体も把握しにくく、ちょっとしたタイプミスでファイルの半分が構文エラーだというようなメッセージがよく出る。Rustの既存のC系文法の方がずっと楽だ。要するに、OCamlの利点はビルドが速いことくらいだが、それだけではわざわざ使う理由としては弱い
だからMLスタイルでプログラミングしたいときは、RustよりもKotlin、Scala、F#を先に見る。そして最近はJavaやC#ですらすでに十分多くのML要素を取り入れていて、大きな抵抗感はない。Caml LightやObjective Camlの時代からML型システムに慣れているが、最近人々がRustに熱狂しているのを見ると、まるでRustがML型システムを新しく持ち込んだかのように錯覚しているように思える
OCamlがもう少しうまく準備していてほしかったという意見については、実際には言語選択の幅が広いことこそ最大の利点だと思う。イギリスだけ見ても(人口は少なくても)非常に多様な言語が共存している。たとえばヨーロッパの死語であるコーンウォール語も最近は地元の人々によって復活しているし、羊飼いたちの間ではクーブリックという数を数える言語も残っている。私も次の世代の家系図のために、OCamlベースのGenewebというプログラムを使い始めた(TMGというWindowsアプリから移行)。家族データが14万人分も入っている。GenewebがOCamlで作られているので、この言語への関心が高まった。もしプログラミング言語が難しいと感じるなら、家系図や系譜学をやってみることを勧める。すぐにGEDCOMという規格のせいで頭を抱えることになるだろう
OCamlは私の大好きな言語のひとつだ。最も多く手がけたのは、Writer's Festivalの運営のためにCRUDアプリをOCaml(ReasonMLベースのJSX)、Dream、HTMX、DataTablesで100%実装したことだ。モジュールでフロントエンドのテンプレートを再利用し、データモデルに変更が入るとコンパイラがどこで壊れたかをすぐ示してくれるので、とても満足していた。ExcelデータをまともなDBへ移したり、.odt形式のテンプレート日程表やサーバーディスクを経由せずにそのままzipファイルへエクスポートしたりと、OCamlのエコシステムの中で驚くほど多くのことを実現できた。ただ、DBクエリをすべて文字列で書かなければならず、型変換も手作業だったので、疲労感が非常に大きかった(コンパイル時の型チェックが効かない)。認証システムも自分で実装しなければならず、コア製品の開発ではない部分にあまりに多くの時間を費やしがちだった。いろいろな言語を見て回って感じたのは、完璧な言語など存在しないということだ。どの言語にもそれぞれ固有の欠点がある。今は自分専用のアプリをRailsで作っているが、必要なものはほとんどすべてデフォルトで提供されるので、言語よりも実際のレイアウト設計や実際のデプロイといった本来の作業に集中でき、ずっと満足している
DarkLangは最初はOCamlで開発されていたが、後にF#へ移行した。その主な理由はライブラリエコシステムと並行性だった(関連記事)。私は.NETに慣れているので多少のバイアスはあるかもしれないが、退屈な部分にも選択肢がよく用意されていて、本質的な問題に集中できる。F#を仕事でかなり使った経験があり、人気のあるUIライブラリも保守しているが、言語エコシステムが小さいため、.NETでも解決策がいつもすぐ見つかるわけではない。だから主流から外れた言語を選ぶときは(たとえばC#の代わりにF#)、コストがかかることを念頭に置くべきだ。OCamlも同様で、強力な言語を提供してくれるが、主流ではないためにさまざまな不便さがある。いくつかの会社が本番サービスで使ってはいるが、彼ら固有の独特な要求に合わせた事例だ
OCamlを数年間好きになろうと努力してみたが、最も不便だったのは「任意のオブジェクトをprintできないこと」だった。ppxでto_string関数を自動導出できるが、設定が面倒で、Rustに比べて使い勝手が落ちる。Set、Mapなどの型を出力するには追加作業が必要だ(参考事例)。golangでは
%vフォーマットでほぼ何でも簡単に出力できるが、OCamlはその点でひと手間かかる%vフォーマットも完璧ではなく、さらに深くポインタをたどるにはgo-spewのようなライブラリが別途必要だ。Pythonの__repr__方式が今まで見た中では最も便利だったOCamlを直接使ったことはないが、F#で作業した経験は非常に快適だった。最近のLLM時代には、関数型言語をもう一度見直してみるのもよいと思う。OCamlやHaskellのような関数型パラダイムでは、情報を小さなテキストに効率よく圧縮できるだけに、LLMのコンテキストウィンドウにもより多くの意味を詰め込めるのではないかと思う。Java、C#、Rubyに比べて、より複雑な変更も一度に適用できるか実験してみる価値がありそうだ
私も最初はそう思っていたが、実際に大規模なHaskellコードベースで働いて考えが変わった。学習データセットにFPが足りないせいか、より簡潔な言語の方がむしろLLMには合わないようだ。コードがverboseであるほど、LLMが誤ったトークンを予測した後に自分を修正する機会が増え、結果的により正しいコードを生成する感じがある
個人的な実験としてC++とHaskellで簡単なCLIゲームを作ってみたが、行数はHaskellの方が少なかったものの単語数はほぼ同じで、コードが「横に広く見える」だけだった。Javaやもっと明示的な言語と比べたわけではないが、プログラムの性質によって適したスタイルは変わると思う。命令型スタイルが向くものもあれば、関数型が向くものもあるかもしれない
LLMのコード生成能力がもう少し進歩したら、本当に強力な型システムとエフェクトシステムでコードの振る舞いの範囲を制約できるとよいと思う。たとえば依存型があれば、「この関数は必ずソート済みのリストを返す」や「この関数は必ず有効な数独の解を返す」といった条件をコンパイル時に検査できる。そこにエフェクトシステムを組み合わせれば、「この関数は有効な数独の解を返すが、ネットワークやファイルシステムにはアクセスしない」と指定できるようになる。LLMがさらに進化すればPythonでもこの程度はできるようになるかもしれないが、進歩が遅いなら、信頼性の低いLLMを信頼性の高い決定的システムで包んで活用する方向が未来だと思う
Scalaでcats-effect(エフェクトライブラリ)を使うとき、LLMの助けで開発速度がものすごく上がった。cats-effectのコードは簡単な概念ですら難しく感じることが多いが、LLMに「cats-effectで〜するにはどうすればいい?」と聞くだけで80%はすぐ解決する。残りの20%は追加のコンテキストを与えればよい。保守性の観点ではまだ検証中だが、エフェクトベースの関数型プログラミングの挫折感は大きく減った。次はClaude Codeがどれだけうまくやるか試してみたい
HaskellにはLLMコード生成において大きな利点が2つある。第一に、表現力の高い型システムが多くのミスを捕まえるので、発生したコンパイルエラー自体をそのままLLMへフィードバックできる。第二に、プロパティベーステスト(QuickCheckなど)による効率的で正確なコード改善がしやすい。LLMはテスト自体をうまく書けないこともあるが、こちらで追加してやれば生成されたコードのバグもすぐ見つかる
この記事を読んで、「なぜF#ではなくOCamlを使わないのか?」という質問に終止符が打たれた。OCaml関連のスレッドではほぼ毎回、「F#を使えばツールの問題は解決では?」という提案が出る。私もOCamlに興味があり、「型付きGo」というあだ名を見て関心を持ったが、まだOCamlそのものに完全な魅力は感じていない。Erlang、Ruby、Rust、Zigなど他の言語コミュニティの熱量とは何かが違う
私はむしろF#のツールエコシステムを避けたくてOCamlに移った人間だ。F#では、私が使っていた頃はコンパイラが遅く、C#中心のエコシステム、弱くてドキュメントも乏しいMSBuild、しょっちゅうクラッシュするIonide、信頼できないFantomasなど、ツール面の問題が多かった。ただ、OCamlもF#の性能志向の機能(たとえば値型などCLRが支えている部分)をすべて代替できるわけではない。そういう意味で、いまだにシンプルなML系言語を見つけられていない。将来的にOxCamlなどが解決してくれることを期待している
最近はOCamlをあまり使っていないが、言語の核そのものは今でも最も好みだ。私のコードスタイルは大きなひとつの関数に偏りがちなのだが、OCamlでは自然とそれを避けるようになる。Rustもサイドプロジェクトでは使っているが、実際にはOCamlの方が楽だ。そういう理由もあって、F#もぜひ一度試してみたい
用語について質問がある。記事では関数型を「指数型(exponential types)」と呼んでいるが、高階関数の型がなぜ指数型と呼ばれるのかよく分からない
すでに良い説明はあるが、より深い理由は関数型が代数的に指数法則に従うからだ。たとえば A → (B → C) はカリー化により (A × B) → C と同型である。これは
(c^b)^a = c^(b·a)に似ている。そして (A + B) → C は (A → C) × (B → C) と同型で、これはc^(a+b) = c^a·c^bの規則に対応する一階の関数型でもすでに指数的だ。たとえばsum型はケースの数だけ値が存在する(例:
A of bool | B of bool→2+2=4通り)。積型と指数型も同様だ。bool -> boolなら可能な値は2^2 = 4個ある(副作用を考えなければ)普通ADT(Algebraic Data Type)について語るときはsumとproductだけを扱う。関数はデータではないので、あまり言及されない。しかし
a -> b型にはb^a通りのケースがあるので、同じやり方で考えられる私も同じ疑問があったが、数学的には和(sum)、積(product)の次が指数(exponent)だから、比喩的にそう呼ぶのだと思う
返信はどれも正しいが、実際には圏論(category theory)で関数型を「exponential product」と呼ぶ。その名前も、AからBへの関数の個数がBの濃度のA乗で数えられることに由来している
sum-typeのケースは型コンストラクタ(type constructor)による値(expression)なので、当然型を持つ。たとえば、
各ケースには型が与えられる。パターンマッチングのおかげで、型コンストラクタのパラメータはその場でアンパックできる。ケースを別個の型として切り出すと、sum-typeが持つexhaustiveness(漏れ防止)の利点が失われ、かえって誤ったプログラム状態を表現できてしまう。sum-typeは一度宣言して何度も使え、たいていは使い捨てだ。コードの読みやすさも重要なので、冗長さが過小評価されている面がある。ちなみにC#/Javaは本当のsum-typeをサポートしていない。下の例を見ると、C#はOOP的なやり方のせいで不必要に複雑だ
MLではずっと簡潔に書ける
2つの方式はほぼ同等だが、C#のOOP要素がむしろ足かせになっている
OCamlではGADTや多相バリアント(Polymorphic Variants)などを使って、それぞれを別個の型のように扱うこともできる。ただ一般には、sum-typeを分離すると一般化もしづらく、理解も難しくなる。型の等価性やvarianceの問題も伴う
わざわざsum-typeとsealed-typeの論争をする理由がよく分からない。私は関数型言語の方を好むが、型レベルでの識別性のおかげで、sealed型だけでもsum-typeはすべてモデル化できるし、サブタイピングのおかげで定義や利用がしやすい面もある。システムのパラダイムは大きく異なるが、数学的にはほぼ同等で、OOPでもFPでも、言語が許す範囲なら型レベルの工夫はほぼ何でも実装できる
Java/Kotlinのsum-type宣言の冗長さは受け入れる価値があるという意見には同意しない。JVM言語によくある典型的なボイラープレートに見える
ReasonMLの文法をこれだけよく知っている人に、長所と短所を比較してほしい。(記事では少し触れられていただけなので)
私が一番惜しいと思ったのはlet binding(公式ドキュメント)だ。ReasonMLではモナド用の
>>=のような演算子を自分でカスタムして扱いやすかった。rescript(ReasonMLのfork)にはそれがまだない。その代わりasync/await構文はよくサポートされていて、非同期コードでは助かる。Melange(記事で少し触れられていた)はReason構文でlet bindingをサポートしている。だからReactベースのフロントエンドでは、MelangeのReason MLは非常に有利だ。let bindingのおかげで(JSXと合わせて)モナディックスタイルの非同期コードもきれいに書ける。OCaml構文ではPPXで回避できるが、エディタでのハイライトがあまりうまくいかない。バックエンドについて言えば、私はPythonスタイルが好きなので、中括弧が今でも気になるし、関数の呼び出しや定義を括弧なしで書くのが好きだ。ただ、初心者のOCamlユーザーとして、変数以外を引数に渡すときの括弧がややこしく、今でも難しく感じる。この体験が参考になればうれしいReasonMLはあまり使ったことがないので、利点は感じられなかった。4年の間に2回も死んでしまったこと以外は……
Reason構文がもっと広まってほしいとは思うが、OCamlコミュニティとやり取りするなら標準構文をそのまま覚える方がよい。コードやドキュメントのほとんどは標準構文なので、結局は知っておく必要がある
私が経験したReasonMLで一番不便だったのは、LSPがまともに動かなかったことだ
dependency injectionをeffects systemで実装する部分をもっと詳しく説明してほしい。パターンマッチングでテスト用の値/本番用の値を束縛するというアイデアは面白そうだが、文章だけではピンと来ない。それにmodule systemに独自の型システムがあるというのも初めて知って興味深い