OCamlで自分で定義した演算子でOptionを楽して扱う

モダンなプログラミング言語には大体用意されているOption。 とても便利な機能ですが、ちょっと使い勝手が悪い(面倒くさい)場面が多々あります。

例えば、データベースからレコードを検索してきて、特定の年齢以上ならそのIDとユーザ名を出力する処理を考えます。 データベースの検索結果はoptionに包まれます。

type t = {
  id: int;
  age: int;
  name: string;
}

(*DBから値を取得する的な関数*)
let find_by_id id : t option=
  Some {id; age=35;  name="Koji"}

(*35歳以上なら特殊な健康診断受けなさい*)
let is_required_special_medical_checkup (user: t option) =
  match user with
  | None -> None
  | Some v -> if v.age >= 35 then user  else None

(*user(type t)を表示する関数*)
let printer (user: t option) =
  match user with
  | None -> None
  | Some v -> Some(Printf.sprintf "%s(%d)" v.name v.age)

let () =
  let v = 10 |> find_by_id |> is_required_special_medical_checkup |> printer in
  let () = match v with
    | None -> ()
    | Some v -> print_endline v;
  in

  print_endline "終了";

optionとmatch式を使って安全なコードを書けました。

しかし、関数is_required_special_medical_checkupと関数printerに、

NoneならNone、Someなら何かしてSomeに包んで返す

という全く同じ処理が含まれています。 今は関数が2つだけなので別に構いませんが、当然optionを扱うということはこのようなコードが至るところに出現してきます。

問題なのは、optionの値を扱うのに毎回match式を書かなければならないことです。 そこで、optionをもっとスマートに扱えるように、自身でオリジナルの中間演算子を定義します。

(* optionを便利に扱う中間演算子 *)
let (>>?) v f = match v with | None -> None | Some v -> Some(f v);;

上記のコードを使った実際のコードは以下の通り。

type t = {
  id: int;
  age: int;
  name: string;
}

(* optionを便利に扱う中間演算子 *)
let (>>?) v f = match v with | None -> None | Some v -> f v;;

(*DBから値を取得する的な関数*)
let find_by_id id : t option=
  Some {id; age=35;  name="Koji"}

(*35歳以上なら特殊な健康診断受けなさい*)
let is_required_special_medical_checkup (user: t) =
  if user.age >= 35 then Some user else None

(*user(type t)を表示する関数*)
let printer (user: t) =
  Some(Printf.sprintf "%s(%d)" user.name user.age)

let () =
  let v = Some 10 >>? find_by_id >>? is_required_special_medical_checkup >>? printer in
  let () = match v with
    | None -> ()
    | Some v -> print_endline v;
  in

  print_endline "終了";

これで、関数is_required_special_medical_checkupと関数printerから、optionがNoneかSomeかどうかという判断が消えました。

実は、標準でOption.mapという関数も用意されてはいますが、型の定義を見れば分かる通り、渡す関数がOptionではない値を返すようになっています。

('a -> 'b) -> 'a option -> 'b option

もし処理の最初の値がSomeで、それ以降はNoneに変わらない、ということであればOption.mapを使うことも可能です。

公開日:2020/06/02

OCaml

About me

ドイツの現地企業でWeb Developer/System Administratorとして働いているアラフォーおじさんです。

プログラミングとかコンピュータに関する事がメインですが、日常的なメモとか雑多なことも書きます。

Links :
目次