この記事は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を使っていたりする。