blog.anqou.net
rss
author
tags

OCaml で let*Result.bind にして便利に使う

OCaml 4.08 から binding operator が導入され let* などが定義できるようになりました:

let foo x f = (* なんやかんや *)

let (let*) = foo

let _ =
  let* x = call_something hoge in
  piyo
  (* 上記の式は foo (call_something hoge) (fun x -> piyo) と等価 *)

パッと見、何に使うのか良くわからない[1]の機能ですが Result.bind に割り当てておくと Rust の try![2] みたく使えて便利です:

let f1 x = Ok () (* なんやかんややって result を返す関数 1 *)
let f2 x = Ok () (* なんやかんややって result を返す関数 2 *)

let (let*) = Result.bind

let _ =
  let* x2 = f1 x1 in
  let* x3 = f2 x2 in
  (* x3 でなんやかんや *)

まず f1 を呼び、それが失敗したら early return して、成功したら f2 を呼び、それが失敗したらやはり early return して、成功したらその結果を使って何かする、というコードが直感的に書けます。let* が無いと以下のように match with をネストしないといけないので大変です:

let _ =
  match f1 x1 with
  | Error _ as e -> e
  | Ok x2 ->
      match f2 x2 with
      | Error _ as e -> e
      | Ok x3 ->
          (* x3 でなんやかんや *)

さて毎回 let*Result.bind に割り当てるのはまぁまぁ面倒ですが、OCaml 5.4 から追加された Result.Syntax にはこの定義がそのまま入っています。ということで let open Result.Syntax in すればどこでも使えるようになりました。もっと手軽にするのであれば dune ファイルに以下のように書いておくと全てのモジュールでデフォルトで open Result.Syntax されるようになります:

(env
 (_
  (flags
   (:standard -open Result.Syntax))))

ちなみにリファレンスを見ると Result.Syntax には let* の他に let+ が定義されています。let+Result.map に割り当てられているため in 以下で result ではなく中身の値を直接返したいときに使えます。また let*let+ と組み合わせて使えるように and*and+ も定義されています。

注釈

  1. Haskell のモナドや F# の computation expression に慣れ親しんでいる人を除く。

  2. Rust の知識が 2018 年で止まっているんですが、最近は try! マクロってなくなったらしい?というのをこの記事を書いて初めて知りました。そうなんだ。