Functors
Functors are probably one of the most complex features of OCaml, but you don't have to use them extensively to be a successful OCaml programmer. Actually, you may never have to define a functor yourself, but you will surely encounter them in the standard library. They are the only way of using the Set and Map modules, but using them is not so difficult.
What Are Functors and Why Do We Need Them?
A functor is a module that is parametrised by another module, just like a function is a value which is parametrised by other values, the arguments.
It allows one to parametrise a type by a value, which is not possible directly
in OCaml without functors. For example, we can define a functor that takes an
int n
and returns a collection of array operations that work exclusively on
arrays of length n
. If by mistake the programmer passes a regular array to one
of those functions, it will result in a compilation error. If we were not using
this functor but the standard array type, the compiler would not be able to
detect the error, and we would get a runtime error at some undetermined date in
the future, which is much worse.
Using an Existing Functor
The standard library defines a Set
module, which provides a Make
functor.
This functor takes one argument, which is a module that provides (at least) two
things: the type of elements, given as t
and the comparison function given as
compare
. The point of the functor is to ensure that the same comparison
function will always be used, even if the programmer makes a mistake.
For example, if we want to use sets of ints
, we would do this:
# module Int_set =
Set.Make (struct
type t = int
let compare = compare
end);;
module Int_set :
sig
type elt = int
type t
val empty : t
val is_empty : t -> bool
val mem : elt -> t -> bool
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val compare : t -> t -> elt
val equal : t -> t -> bool
val subset : t -> t -> bool
val iter : (elt -> unit) -> t -> unit
val map : (elt -> elt) -> t -> t
val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val cardinal : t -> elt
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val split : elt -> t -> t * bool * t
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
For sets of strings, it is even easier because the standard library provides a
String
module with a type t
and a function compare
. If you were following
carefully, by now you must have guessed how to create a module to
manipulate string sets:
# module String_set = Set.Make (String);;
module String_set :
sig
type elt = string
type t = Set.Make(String).t
val empty : t
val is_empty : t -> bool
val mem : elt -> t -> bool
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val compare : t -> t -> int
val equal : t -> t -> bool
val subset : t -> t -> bool
val iter : (elt -> unit) -> t -> unit
val map : (elt -> elt) -> t -> t
val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val split : elt -> t -> t * bool * t
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
(the parentheses are necessary)
Defining Functors
A functor with one argument can be defined like this:
module F (X : X_type) = struct
...
end
where X
is the module that will be passed as argument, and X_type
is its
signature, which is mandatory.
The signature of the returned module itself can be constrained, using this syntax:
module F (X : X_type) : Y_type =
struct
...
end
or by specifying this in the .mli
file:
module F (X : X_type) : Y_type
Overall, the syntax of functors is hard to grasp. The best may be to look at
the source files
set.ml
or
map.ml
of the
standard library.
Help Improve Our Documentation
All OCaml docs are open source. See something that's wrong or unclear? Submit a pull request.
Contribute