2022/02/23

OCamlのファンクターを理解する

ファンクターとは

OCamlにおいて、基本的にはモジュールを引数に取り、モジュールを返すというものですが、モジュール自体の使い方によって、意味合いが違うものになるのではないかと個人的には思っています。

まずはIntTypeというシグネチャを持つモジュールをOneを考えてみます。


module type IntType = sig
  val i : int
end

module One : IntType = struct
  let i = 1
end

Oneモジュールを使って足し算ができるファンクターを考えてみます。


module Add =
  functor (I : IntType) ->
  functor (J : IntType) ->
  struct
    let i = I.i + J.i
  end

これは以下のように書いても同じです結果になります。


module Add (I : IntType) (J : IntType) = struct
  let i = I.i + J.i
end

ファンクターはモジュールを返すものなので、以下のように使うことができます。


module Two : IntType = Add(One)(One)
module Three : IntType = Add(One)(Two)
module Four : IntType = Add(Three)(One)

この例では、モジュールを変数や関数と同じような形で使っており、以下のように置き換えてみるとほぼ同じ形だと分かります。


let one = 1

let add i j = i + j

let two = add one one
let three = add one two
let four = add three one

ファンクターがどのようなものなのかがわかりやすい形だと思います。しかし、実際にはもっと様々なことができて、例えばGoFのIteratorパターンを考えてみます。


module type Aggregate_intf = sig
  type t
  type iterator
  val create : unit -> iterator
  val add : t -> iterator -> unit
  val get_size : iterator -> int
  val get_current : iterator -> int
  val get_it : iterator -> t list
  val increment_current : iterator -> unit
end

module IntAggregate
  : (Aggregate_intf with type t = int)
= struct
  type t = int
  type iterator = {current: int ref; mutable it: t list}
  let create () = {current = ref 0; it = ([] : t list)}
  let add item it = it.it <- item::it.it
  let get_size it = List.length it.it
  let get_current it = !(it.current)
  let get_it it = List.rev it.it
  let increment_current it = it.current := !(it.current) + 1
end

module Iterator (A : Aggregate_intf) = struct
  type t = A.t
  type iterator = A.iterator

  let has_next it = if 0 < A.get_size it then true else false

  let next it =
    if A.get_current it < A.get_size it then
      let result = List.nth (A.get_it it) (A.get_current it) in
      A.increment_current it;
      Some result
    else
      None
end

module IntIterator = Iterator (IntAggregate)

実際使ってみます


# let a = IntAggregate.create () ;;
val a : IntIterator.iterator = <abstr>
# IntAggregate.add 1 a ;;
- : unit = ()
# IntAggregate.add 2 a ;;
- : unit = ()
# IntIterator.next a ;;
- : int option = Some 1
# IntIterator.next a ;;
- : int option = Some 2
# IntIterator.next a ;;
- : int option = None

この例では、モジュールをクラスのような形で使うことができました。Aggregate_intf with type t = intというところがありますが、これは共有制約というもので、モジュールの中での型と外部の型が同じものだぞ、ということを示したものです。ただ、私の勉強がまだ足らず完全に理解したとは言い難いです。ただ、こうすることで上記のコードは問題なく動くようになりました。

OCamlではこのようなことをしなくても、特定の型をリストに入れてListモジュールを使えばいいのですが、今回は無理矢理作ってみました。ファンクターはこういう使い方もできるようです(正しいかどうかは定かではない)

まとめ

OCamlのモジュールおよびファンクターは結構柔軟に使える代物なのではないかと思います。まだまだ理解が浅いですが、今後より勉強していい感じに使えるようになれたらと思います。