OCaml でオーバーロード
前回の記事 (HListをOCamlで: 型クラス無しで作る異種混合リスト (heterogeneous list)) の続き.
なんと次のコードが合法である:
open HList
open Stdlib
let x = List.nth [1;2;3] 1
(* val x : int = 2 *)
let y = HList.nth [1;"a";false;0.1] _1
(* val y : string = "a" *)
一見して違和感があるのは,後者は型エラーになるようにも見えることだ. つまり,上で open Stdlib
で list
型のリストがスコープに入っているので,[1;"a"] のようなリストは型がつかないのではなかろうか?
これがなぜ合法かといえば,後者の [1;"a";false;0.1]
は型 _ HList.t
と推論されるため,もっといえば open HList
したあとに 1 :: "a" :: false :: 0.1 :: []
と書いたのと同じ効果を持っているためだ.
つまり,リストのコンストラクタ ::
と []
が,状況によって異なるコンストラクタとして解釈されている.
なぜそのような恣意的な選択が可能なのか?そのからくりは HList.nth
が書かれていることである.このため,型推論の過程で,HList.nth
の引数は _ HList.t
でしかありえないと推論される.そこで,直前に open
していた HList のほうの ::
と []
が選ばれた,というわけだ.
私の理解では,このような コンストラクタのオーバーロード はレコードの使い方に端を発する.
レコードのフィールド名は重複することが多い.空間の一点を表すのに x, y, z, ... のような名前を使いたかったり ({x:float; y:float}
と {x:float; y:float; z:float}
),様々なレコードで name とか id とかいう名前のフィールドを共用したいと思うことは頻繁にある.しかしだいぶ古い時代の OCaml ではそういうことはできなかった.
OCamlのどこかのバージョンでレコードのフィールド名のオーバーロードが型推論ベースで解決されるようになった.
つまり,重複するフィールドラベルをもつ型を open
等でスコープに入れておけば,レコードの構築 {f1=e1;..;fn=en}
においては,その式を 囲む文脈から生成される型が決まり,フィールド参照 e.fi
においては,式 e
の型からどの fi
が参照されるかが決まる.
これと同様に,スコープ内で衝突する同名のコンストラクタを持つヴァリアント型についても,型推論の結果を用いて型が選ばれるオーバーロードができるようになっていたわけだ.
以下雑多な内容
上も含め,知っている人にとっては当たり前なのだが,ざっと知っている限りの状況を書き出してみる.
プログラミング言語におけるオーバーロード
このように同じ記法や名前を異なる型において何度も使うことをオーバーロードという.
関数や演算子のオーバーロードはプログラミング言語において普遍的な機能で,思いつく範囲でも以下のものを並べることができる:
- 古くは C言語の算術演算子は各種数値型についてオーバーロードされている.おかげで OCaml のように
1.0 +. 2.0
のように型によって異なる演算子を使う必要がない. - C++ の関数/演算子オーバーロード.テンプレートと組み合わせれば呼び出し先を型の構造に従って選択できる.
- Java のメソッドオーバーロード.単に同じ名前を異なる関数につけられるだけだが例えば System.out.println など使う部分がなくはない.(当時、C++の演算子オーバーロードは邪悪だと思われていたためか Java には入らなかったが、データサイエンス全盛の今となってはちょっとしたハンデのような気がする.)
関数型言語のグループにも次のものがある.
- Haskell の型クラス.静的型付き関数型言語の型推論との相乗効果でプログラムの書きやすさが飛躍的に増大する.
- Scala の implicits.型クラスのインスタンスのスコープを制限できる.
- 他の言語の事情はそこまでよく知らない.Standard ML では
=
を使える型の上を動く型変数を''a
のように特別視するようだ.(数値型に限ってはオーバーロードがあるんだっけ?)
OCamlにおけるオーバーロードの未来
上でも少し触れたように OCaml にはオーバーロードがまだない (Standard ML でもユーザー定義オーバーロードはできなかったような). これは非常に残念なことで,例えば OCaml で数値計算を書いたりモナドを使ううえでの足枷にもなっている.
一応,オーバーロードを使うには次のような道がある:
- Modular implicits. ML Family Workshop 2014 で提案された (arXiv).Scala の implicits にインスパイアされた機能で,モジュールを介して実現できる.が,最近なかなか動きが見えない.OCaml コミュニティでは Multicore OCaml の update は頻繁にやってくるので,今後に期待したいところだが…
- ppx_implicits. 古瀬淳さん aka camlspotter が開発した,プリプロセッサで型推論の結果を利用して型クラス辞書を挿入する超スゴイ実装だが,今は公開されていないのかググると fork が最初にヒットする.
OCamlカルチャーとしては、ppx_deriving などで自動生成したコードを型名のサフィクスを付けて使うようになっている(らしい).