OCamlでOptionな値をチェインする方法
基本的には、Option.map
でOKです。
utop # Some 10 |> Option.map (fun v -> v * 2) |> Option.map (fun v -> v - 1);;
- : int option = Some 19
ただし、途中でNoneを返すような関数があると、コンパイルエラーになります。
utop # Some 10 |> Option.map (fun v -> None) |> Option.map (fun v -> v - 1);;
Line 1, characters 41-68:
Error: This expression has type int option -> int option
but an expression was expected of type 'a option option -> 'b
Type int is not compatible with type 'a option
Option.map
は結果をOptionに包んで返してくれますが、自分でNone
を返してしまっているので、結果が 'a option option
となってしまって、型が合わない状態になっています。つまりOptionの入れ子状態です。
余談ですが、コンパイル段階でこういった問題が発覚するのは本当に素敵ですね!
回避策1 join
Option.join
は、a option option
という入れ子になったOptionを一個のみにしてくれる関数です。
utop # Some (Some 10) |> Option.join;;
- : int option = Some 10
utop # Some (None) |> Option.join;;
- : 'a option = None
コレを使えば、以下のようにできます。
utop # Some 10 |> Option.map (fun v -> Some (v * 2)) |> Option.join |> Option.map (fun v -> v - 1);;
- : int option = Some 19
utop # Some 10 |> Option.map (fun v -> None) |> Option.join |> Option.map (fun v -> v - 1);;
- : int option = None
回避策2 bind
こちらはElmでいうandThen
関数に相当します。
Option.map
と違って、渡す関数では結果を自分でOptionに包んで返してあげる必要があります。
utop # Option.bind (Some 10) (fun v -> Some (v * 2));;
- : int option = Some 20
utop # Option.bind (Some 10) (fun v -> None);;
- : 'a option = None
しかし、よく見ると引数の順番がOption.map
とOption.bind
では違います。
utop # Option.map;;
- : ('a -> 'b) -> 'a option -> 'b option = <fun>
utop # Option.bind;;
- : 'a option -> ('a -> 'b option) -> 'b option = <fun>
Option.bind
の方では、第1引数に処理対象のOption、そして第2引数に処理内容となる関数を渡すようになっています。
そのため、パイプライン演算子|>
を使って処理対象となる値を渡してチェインしていく、ということができません。
そこで、Option.bind
をラップする中間演算子を宣言してあげると記述が楽になります。
utop # let (>>=) v f = Option.bind v f;;
val ( >>= ) : 'a option -> ('a -> 'b option) -> 'b option = <fun>
使い方は以下のようになります。
utop # Some 10 >>= (fun v -> Some(v * 2)) >>= (fun v -> Some (v - 1));;
- : int option = Some 19
utop # Some 10 >>= (fun v -> None) >>= (fun v -> Some (v - 1));;
- : int option = None
ちなみに、>>=
は、Lwt.bind
でも利用されているので、もしLwt
を利用する際には被らないように別のものにする必要が有あります。
まとめ
基本的にはOption.join
よりはOption.bind
を利用して、さらにそのために中間演算子を定義してしまうのがベターだと思います。
ちなみに、当然中間演算子にしなくても普通の関数でもOKです。
普通の関数として宣言しておけばパイプライン演算子|>
でチェインしていくことができます。
utop # let and_then f v = Option.bind v f;;
val and_then : ('a -> 'b option) -> 'a option -> 'b option = <fun>
utop # Some 10 |> and_then (fun v -> Some v) |> and_then (fun v -> Some (v * 2));;
- : int option = Some 20
utop # Some 10 |> and_then (fun v -> None) |> and_then (fun v -> Some (v * 2));;
- : int option = None
とはいえ、コレなら素直に中間演算子として定義しておいてあげたほうが楽ちんだと思います。
公開日:2020/06/15