この記事はML Advent Calendar 2020 9日目の記事です。
OCamlにそろそろ入りそうな機能を紹介します。
今回は、次のOCaml 4.12で入る Eitherモジュールの紹介をします。
背景
Either.t 型はタプル型の双対で、タプル型 'a * 'b が「'a と 'b 両方」を表すのに対して、 ('a, 'b) Either.t は「'a または 'b のいずれか」を表す。
either型はOCaml以外にもHaskellやCoq, Scalaといったさまざまな言語に採用されている(この記事ではCoqの sumbool 型をeither相当として扱う)。
また、Either型には「左派」と「右派」がいることも知られている。コンストラクタが2つだけのinductive typeについて最初のコンストラクタを真値として扱う(if式の仕様)Coqは left を正常値とする左派であり、型パラメータの部分適用ができるHaskellは最後の型パラメータを使う Right が正常値とする右派だ。Scalaは左派、右派どちらでもない中道派だったのだが、2.12(2016年ごろ)から右派になった。
OCamlは、 either は左右中立な型であるべし、ということで、成功/失敗といったバイアスの入ったeither型は別に result 型と呼びならわしている。このような流派には他にRustがある。
そんなOCamlに Either.t が入ったのは、 List.partition_map の導入がきっかけだ。
List.partition_map は ('a -> ('b, 'c) Either.t) -> 'a List.t -> ('b List.t * 'c List.t) のような型の関数で、mapした関数の戻り値に応じてリストをふたつに分割する。
OCamlでは、このような場合は多相バリアントを使い ('a -> [`Fst of 'b | `Snd of 'c]) -> 'a List.t -> ('b List.t * 'c List.t) のようにして、わざわざ型を定義せずに済ませる人もいた(containersライブラリとか)。今回の List.partition_map のPRも最初は result 型で書かれていたが、 result 型の Ok/Error をそのような意味で使うのは気持ち悪いということで、 Either.t が導入されることになった。
実装
前置きはこのくらいにしてEither モジュールのシグネチャ を見てみよう。とは言っても、実装上面白いことは特にないので型定義と map まわりの関数の型だけ見てみる。
type ('a, 'b) t = Left of 'a | Right of 'b
val map_left : ('a1 -> 'a2) -> ('a1, 'b) t -> ('a2, 'b) t
val map_right : ('b1 -> 'b2) -> ('a, 'b1) t -> ('a, 'b2) t
val map :
left:('a1 -> 'a2) -> right:('b1 -> 'b2) -> ('a1, 'b1) t -> ('a2, 'b2) t
最初に言っていた通り、OCamlの Either.t は左右に偏らない型なので、 Left にmapする map_left と Right にmapする map_right があり、両方にmapする関数を map と呼んでいる。ラベル付き引数を使っているのがOCamlの標準ライブラリとしては新しめなスタイルだろうか。
他の関数も同様に *_left と *_right が用意されている。
おまけ: 他のML語族の言語のeither型
OCamlの御先祖様であるLCF MLには α + β 型という形でeither相当の型が存在した。 α × β なタプル型が生き残ったのに対し、 + の方は現代のMLには受け継がれなかった。この型の値は inl : α → α + β や inr : β → α + β といった関数でつくる。
Standard ML 97のBasisライブラリにはeither型は存在しない。 Successor MLではEitherモジュールが提案されており、こちらもOCamlと同じく左右の偏りのない定義になっている。
F#でeither型にあたるのは Choice<'T1, 'T2> 型だ。判別共用体のタグ名も Choice1Of2 や Choice2Of2 と、leftやrightといった言葉すら使っていない(F#はリストの畳み込みもfoldとfoldBackだったりする)。似たような命名法として、OCamlのJanestreet baseライブラリでは type ('f, 's) t = | First of 'f | Second of 's のようにfirst, secondを使っていたりする。