13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OCamlでFree Applicative Functors

Last updated at Posted at 2015-09-24

Monad に対して Free Monad があるように、 Applicative に対しても Free Applicative Functor がある1 2

OCaml で定義すると下のようになる。

open Higher

type (_, _) t =
  | Pure : 'a -> ('a, 'f) t
  | Apply : ('a -> 'b, 'f) t * ('a, 'f) app -> ('b, 'f) t

Pure はちょうど Applicativepure をそのままバリアントとして表したものになっていて、 Apply<*> を表現したものになっている。OCaml では 'a ff の部分(型構成子)をパラメータ化することができないので、higher ライブラリを使って 'a f('a, f) app と読み替えている。こうすれば、 f の部分を型変数にできるので OCaml でも扱える。また、 Apply の方の 'a は結果型に表れない存在型になっているので GADTs が必要になる。ここでは Apply の関数の型を t、引数の型を app にしたが、これを逆にしたものもこれと同型であることが証明されている(らしい。元論文を参照)。

あとは Applicative のメソッドを定義する。

let pure v = Pure v

let rec (<$>) : 'a 'b. ('a -> 'b) -> ('a, 'f) t -> ('b, 'f) t =
  fun f -> function
    | Pure v -> Pure (f v)
    | Apply (ga, v) -> Apply ((fun g x -> f (g x)) <$> ga, v)

let map f a = f <$> a

let rec (<*>) : 'a 'b. ('a -> 'b, 'f) t -> ('a, 'f) t -> ('b, 'f) t =
  fun fa xa ->
    match fa with
    | Pure f -> f <$> xa
    | Apply (ga, v) ->
        Apply ((fun g x y -> g y x) <$> ga <*> xa, v)

let app f a = f <*> a

pure は単に Pure で包むだけでよい。 <$>map)は Pure の場合は内側の値に f を適用して再度包み直し、 Apply の場合は関数の側に f を合成する。 <*>app)は関数が Pure であれば得られた関数を map し、 Apply のときは v('c, 'f) appga('c -> 'a -> 'b, 'f) t で、 xa('a, 'f) t のような感じなので、 'a の部分を部分適用すると全体が ('b, 'f) t になって型が合う。型を追うと理解しやすいと思う。ここで、 <$><*> は呼び出されたときの型と、その中で再帰呼び出しされるときの型が異なる多相再帰になっているので、自分で多相型アノテーションを書かなければならない。

さらに、実際に副作用を表す型をこの型に持ち上げる。型としては ('a, 'f) app('a, 'f) t に変換したい。 t の定義を見ると、 ('a, 'f) app が入る場所は Apply しかないので、 Apply の関数側に何もしない関数を入れておく。

let id x = x

let lift v =
  Apply (pure id, v)

たぶん大丈夫なので、実際のインスタンスを定義していく。まずは list

module L = Newtype1(struct type 'a t = 'a list end)

let llift v = v |> L.inj |> lift

let flat_map f xs = List.flatten (List.map f xs)

let rec run_list : 'a. ('a, L.t) t -> 'a list = function
  | Pure v -> [v]
  | Apply (fs, vs) ->
      let fs = run_list fs in
      let vs = L.prj vs in
      fs |> flat_map (fun f -> vs |> List.map f)

let pl x =
  x |> [%derive.show: (int * int) list] |> print_endline

let la =
  (fun x y -> (x, y)) <$> llift [1; 2; 3] <*> llift [5; 6; 7]

let () = run_list la |> pl

higher ライブラリの Newtype1 ファンクタで 'a list('a, L.t) app を相互変換するモジュールを作成する。 llift'a list -> ('a, L.t) t'a list を Applicative な値にする。

run_list('a, L.t) t に解釈を与えるもので、 Haskell の Applicative [] と同じく、取り得る値すべての組み合わせを計算するような解釈をしている。

OCaml で複雑な構造の値を表示しようとすると何かと面倒なのだけれど、今回は ppx_deriving で楽をしている。

同様に Haskell の ZipList のような解釈を書くこともできる。

let rec map2 f xs ys =
  match xs, ys with
  | [], _ | _, [] -> []
  | x::xs, y::ys -> f x y :: map2 f xs ys

let rec run_zip_list : 'a. ('a, L.t) t -> 'a list = function
  | Pure v -> let rec vs = v::vs in vs
  | Apply (fs, vs) ->
      map2 (@@)
        (run_zip_list fs)
        (L.prj vs)

let () = run_zip_list la |> pl

こちらは zipWithN のように値を組み合わせる。 OCaml の List.map2 は引数のリストが同じ長さでないといけないので新たに map2 を自前で定義している。 Pure の方の解釈は Recursive definitions of values の条件に合致しているのでこのような書き方ができる。

というように、リストに対する Applicative のインスタンスが複数ある場合でも ZipList のような型の別名を定義しなくても、解釈関数を別に用意することで対応できる。

もちろん、リスト以外にも option のような別の型にも対応できる。

module O = Newtype1(struct type 'a t = 'a option end)

let olift v = v |> O.inj |> lift

let rec run_option : 'a. ('a, O.t) t -> 'a option = function
  | Pure v -> Some v
  | Apply (f, v) ->
      match run_option f, O.prj v with
      | None, _
      | _, None -> None
      | Some f, Some v -> Some (f v)

let po v =
  v |> [%derive.show: string option] |> print_endline

let () = run_option begin
  Printf.sprintf "%d, %f" <$> olift (Some 42) <*> olift (Some 3.14)
end |> po

let () = run_option begin
  Printf.sprintf "%d, %f" <$> olift (Some 42) <*> olift None
end |> po

let () = run_option begin
  Printf.sprintf "%d, %f" <$> olift None <*> olift (Some 3.14)
end |> po

ここでも ppx_deriving が活躍している。

今回の OCaml での Free Applicative の定義と、おまけで Freer Monad の定義は gist に置いてある。

OCaml で Haskell 風の Monad や Applicative スタイルのプログラムを真似しようとすると、型クラスの辞書をひとつひとつ自分で渡すことになったり、 ppx_implicits のような外部ライブラリに頼ったり、 modular implicits のようなコンパイラ拡張が公式に採用されることを夢見たり、と、どうも大掛かりな感じになってしまう。今回の Free Applicative Functor や、 Freer Monad のようなものだと少し気軽に使えないだろうか。

  1. Paolo Capriotti and Ambrus Kaposi: "Free Applicative Functors", Proceedings 5th Workshop on Mathematically Structured Functional Programming, (MSFP 2014), Grenoble, France, 12 April 2014. EPTCS 153, 2014, pp. 2–30

  2. Scala で紹介した記事もある。Free Applicative Functors in Scala

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?