OCamlには型クラスはないが、ファンクターでそれっぽい雰囲気のものは作れそうだなと思ったので試してみた。

まずはモノイドを表すモジュール型を定義。シグネチャの関数名は zerocombine でも好きな名前で良いと思います。

module type Monoid = sig
  type t

  (* 単位元 *)
  val empty : t

  (* 二項演算。実装では結合法則を守る必要がある *)
  val append : t -> t -> t
end

ファンクターは以下のように MakeMonoid とした。

(* ファンクター *)
module MakeMonoid (M : Monoid) = struct
  type t = M.t

  let empty = M.empty
  let append = M.append
end

(* Monoidのシグネチャを満たしたモジュールを定義 *)
module MyInt = struct
  type t = int
  let empty = 0
  let append = ( + )
end

(* int専用のMonoidモジュールを作成 *)
module IntMonoid = MakeMonoid (MyInt)

IntMonoid を使ってみる。当然のようにint型の二項演算ができる。

IntMonoid.empty
(* - : int = 0 *)

IntMonoid.append 10 10
(* - : int = 20 *)

IntMonoid.append 10 (IntMonoid.append 10 IntMonoid.empty)
(* - : int = 20 *)

IntMonoid.append (IntMonoid.append 10 IntMonoid.empty) 10
(* - : int = 20 *)

これだけだと味気ないので畳み込みを実装してみた。

(* Foldableなモジュールを作成するファンクター *)
module MakeFoldable (M : Monoid) = struct
  include MakeMonoid (M)

  let fold = List.fold_left append empty
end

(* int専用のFoldableモジュールを作成 *)
module IntFoldable = MakeFoldable (MyInt)

IntFoldable.fold [1; 2; 3; 4; 5]
(* - : int = 15 *)

まぁ int 単体だと旨味が少ないかもしれないけど、自作した型を畳み込みしたい!とか、既存の型が実はモノイドの要件を満たしていたので畳み込みしたいなというケースで使えそう。

type row = {
  a : int;
  b : int;
  c : int;
}

module RowFoldable = MakeFoldable (struct
  type t = row
  let empty = { a = 0; b = 0; c = 0 }
  let append x y = { a = x.a + y.a; b = x.b + y.b; c = x.c + y.c }
end)

let row1 = { a = 100; b = 100; c = 100; }
let row2 = { a = 100; b = 200; c = 100; }
let row3 = { a = 100; b = 300; c = 100; }
let rows = [row1; row2; row3;]

RowFoldable.fold rows
(* - : row = {a = 300; b = 600; c = 300} *)

fold_map

fold_map を実装する場合は少し工夫がいる。fold_map に渡す関数の戻り値を畳み込む必要があるため、ファンクターの第二引数でも Monoid を渡してこちらで処理するように変える。

module MakeFoldable (M : Monoid) (O : Monoid) = struct
  include MakeMonoid (M)

  type out = O.t

  let fold = List.fold_left append empty
  let fold_map f = List.fold_left (fun x y -> O.append x (f y)) O.empty
end

module ListIntFoldable =
  MakeFoldable
    (struct
      type t = int list

      let empty = []
      let append = List.append
    end)
    (MyInt)

ListIntFoldable.fold [ [1;2]; [3;4;5]; [6] ]
(* - : int list = [1; 2; 3; 4; 5; 6] *)

ListIntFoldable.fold_map (fun xs -> List.length xs) [ [1;2]; [3;4;5]; [6] ]
(* - : int = 6 *)