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