Let's say I have a module M
parameterized by a module F
:
module M (F : sig type id type data end) =
struct
type idtype = F.id
type datatype = F.data
type component = { id : idtype; data : datatype }
let create id data = { id; data }
let get_comp_data comp = comp.data
let get_comp_id comp = comp.id
end
so I use it like this :
module F1 = struct type id = int type data = float end
module MF1 = M(F1)
let comp = MF1.create 2 5.0
let id = MF1.get_comp_id comp
Now, if I want M
to match signature S
:
module type S =
sig
type idtype
type datatype
type component
val create : idtype -> datatype -> component
val get_comp_data : component -> datatype
val get_comp_id : component -> idtype
end
module F1 = struct type id = int type data = float end
module MF1 = (M(F1) : S)
let comp = MF1.create 2 5.0
let id = MF1.get_comp_id comp
what bothers me here is, in order to define get_comp_data
and get_comp_id
, I need to
specify idtype
and datatype
in module S
; now just imagine I have other records types in M
with their own types, I'll have a dozen types to specify in S
? Is there a simpler way to avoid that?
Parameterized modules are to modules what functions are to base values. Just like a function returns a new value from the values of its parameters, a parameterized module builds a new module from the modules given as parameters. Parameterized modules are also called functors.
A functor is a module that is parametrized by another module, just like a function is a value which is parametrized by other values, the arguments. It allows one to parametrize a type by a value, which is not possible directly in OCaml without functors.
ocaml modules allow to package together data structures definitions and functions operating on them. This allow to reduce code size and name confusion, as any identifier can appear in different modules, with different meanings, without any interference.
The natural way to do this is to seal the module at the definition site, not the use site. Then you just need to express the type sharing once:
module M (F : sig type id type data end) :
S with type idtype = F.id and datatype = F.data
= struct ... end
If your functor parameter is more complex then you can also just share an entire module instead of individual types. For example:
module type TYPES = sig type id type data (* ...and more... *) end
module type S =
sig
module Types : TYPES
type component
val create : Types.id -> Types.data -> component
val get_comp_data : component -> Types.data
val get_comp_id : component -> Types.id
end
module M (F : TYPES) : S with module Types = F
= struct ... end
Or you can even parameterise the signature itself, by nesting it into another functor:
module type TYPES = sig type id type data (* ...and more... *) end
module S (F : TYPES) =
struct
module type S =
sig
type component
val create : F.id -> F.data -> component
val get_comp_data : component -> F.data
val get_comp_id : component -> F.id
end
end
module M (F : TYPES) : S(F).S
= struct ... end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With