ちょっとしたCLI(ある程度出来たら記事にします)をOCamlで作っています。その際、初めてppx_letを使ってみたんですが、なかなか良かったので紹介します。

ppx_letは、OCaml界隈では知らない人のいないJane Street社が公開しているPPX拡張です。

OCamlでmonadを扱う時の課題

大抵、resultとかoption(特にresult)をmonadicにつなげていく時、大抵は下のような書き方になります。

let bind v ~f = match v with
  | Ok v -> f v
  | Error _ as v -> v
let (>>=) v f = bind v ~f

let foo = function
  | v when v > 10 -> Ok v
  | _ -> Error "error"


let bar v = Ok (v * 10)

let () =
  let v = foo 12 >>= bar in
  match v with
  | Ok v -> Printf.printf "result %d\n" v
  | Error e -> Printf.printf "error %s\n" e

>>= とかのoperatorを使うのが一般的かと思います。これは非常にシンプルな例なので、operatorでも特に困りません。

しかし、ある程度の規模になってくると、ある時点で取得したデータを、後に使いたい、ということが非常によくあります。

foo v >>= fun v1 ->
bar v1 >>= fun v2 ->
foobar v1 v2

これが続くと、特にreadabilityの点で問題になってきます。

これらを解決するために、PPX拡張が色々出ています。lwt用にlwt_ppxとかあったりします。

ppx_letの特徴

詳しくはppx_letのreadmeを読むのが早いのですが、overviewとして次のように書かれています。

The aim of this rewriter is to make monadic and applicative code look nicer by writing custom binders the same way that we normally bind variables

すごい簡単に言うと、MonadicとかApplicativeが混ざったコードを読みやすくするためのPPX拡張です。

ppx_letは、特定のMonad/Applicativeに閉じず、 *特定のmodule定義があれば動作します* 。この辺りの汎用性が高いです。

ppx_letで書いてみる

先に出た例は、以下のように書き換えられます。

module Let_syntax = struct
  let bind v ~f = function
    | Ok v -> f v
    | Error _ as e -> e

  let map v ~f = function
    | Ok v -> Ok (f v)
    | Error _ as e -> e
end

let%bind v1 = foo v in
let%map v2 = bar v1 in
foobar v1 v2

ppx_letは、対応する型に対して、 Let_syntax というmoduleが定義されていることを要求します。基本形として、次のextensionを提供しています。

どういう変換か?は、各構文においては割と自明なんですが、mapとbindの違いがちょっと分かりづらいです。この違いは、 bind はlet expression全体をそのまま利用する、 map はlet expression全体をmonadに包む、という点の違いです。

ppx_letのいい所

monadを使う場合はppx拡張を使おう

ほとんどのアプリケーションでは、何かしらのMonadを使うかと思います。そうでなくとも、optionやresultは多用されると思います。

ひたすらnestしたmatch書いてるなー、とか、operatorが10個とか続いて心がすさんでいると感じたら、ppx_letを試してみてはいかがでしょうか。少しは心の平穏を得られるかもしれません・・・。