OCamlのfunctorがよくわからなかったので調べて理解した限りのことを書く。間違ってたらすみません。
・declaration of structure
module Module_name = struct (* implement *) end
・declaration of signature
module type SIGNATURE_NAME = sig (* declare *) end
・application of signature
module Module_name : SIGNATURE_NAME = (* struct implement end or only original_module_name *) (* or *) module Module_name = (original_module_name : SIGNATURE)
・abstruction of signature
(* in dec of signature *) module type SIG_NAME = sig type 'a t (* only type name *) end
シグネチャの宣言でtypeの右辺を記述せず、適用する実装の内部だけで宣言していれば、実際の使用のときには内部の型は抽象化して隠蔽することができる。
・sentence and application of functor
(* declare *) module Module_C = functor (parameter : SIGNATURE) -> struct...end (* apply *) module Module_B = Module_C (Module_A_as_parameter)
・functorはパラメータとしてモジュールAを受け取って、シグネチャを適用して、その受け取ったモジュールAをstruct...endの内部で利用して、このstruct...endで定義される新しいモジュールBを返す。この意味で「モジュールAを受け取りモジュールBを返す関数」と呼ばれる。functorを適用するときは、functor宣言で返されることになるモジュールBに引数として(SIGNATUREを適用し内部で利用する)モジュールAを渡す。このfunctor構文を含むモジュールをモジュールCとする。上に書いた定義文に当てはめるとこんな感じ。
・functorはmoduleだけでなくsignatureに対しても可能。
module type MULTISET = functor (T : ORDERED_TYPE) -> sig type t val empty : t val add : T.t -> t -> t val count : T.t -> t -> int end
このとき、T.tはTで渡されたモジュールの下で宣言されているtypeであって、functorで渡される内部のtypeとしてのtとは異なっている。だから例えばTの実装ではtype t = stringだったとしても、MULTISETが適用する実装の中でtype t = treeと宣言されていれば、MULTISETが与えるsignatureの実体は
val add : string -> tree -> tree
である。signatureによって抽象化することで、実装が変わっても(実装を変えるときはまさに"string"を"int"に変えたりするだけでよい)インターフェースを変えずに使い続けることができる(利用するときは(dot)tでいいのだから)。実装変更の手間が減り、再利用のしやすさが増す。
さらに言うと、「どうしてわざわざ"signature MULTISETにおけるtype t"と"module Tにおけるtype t"のどちらも"t"の文字を一緒にしてるんだ」と思われるかもしれないが(現に私がそう思っただけなのだけど)、type宣言も"t"という一文字で統一することによって、signatureにおけるヴァリアント型の利用も抽象化することができる。つまり「どのモジュールでも内部のヴァリアント型宣言はtで行う」ということにしておけば、「どのシグネチャでも内部のヴァリアントへはtでアサインすればよい」と命名や実装の差異に煩わされることなく宣言できる。
こんな感じか。宣言と定義ってどう違うんだろうって調べたらたしかに宣言は外部変数の宣言も含むけど定義は同一のスコープについて行われるようなイメージだ。頭の中で区別できてなかった。