Modular implicits is an experimental feature of OCaml that has yet to land on the master branch. In this and upcomings posts I’m going to give a few examples of what it brings to the table. For an introduction to the topic it’s best to read the original paper.

To run the code below I’m using a branch of the OCaml compiler available via:

opam switch 4.02.0+modular-implicits
eval `opam config env`

At first we’ll take a look at functors, that is the functor pattern and not module functors. A functor, as inspired by the Haskell type class, can be represented in OCaml by a module signature:

module type FUNCTOR = sig
  type 'a t
  val map : ('a -> 'b) -> 'a t -> 'b t
end

The signature states that a functor is a module with some type 'a t along with a map function. Before looking at any concrete instances, we can already make use of modular implicits and define the following (generic) map function:

let map {F : FUNCTOR} = F.map

Here, map is a function that takes an argument F of type FUNCTOR module and applies its map function. It has the following signature:

val map : {F : FUNCTOR} -> ('a -> 'b) -> 'a F.t -> 'b F.t

The interesting bit is that the instance of F may be deduced by the context in which map is invoked and applied implicitly. For example:

map String.length ["list"; "of"; "strings"];;
map sqrt (Some 9.);;

Both these invocations of map are valid given that there exists in scope implicit modules implementing the FUNCTOR signature for the list and option types:

implicit module ListFunctor = struct
  type 'a t = 'a list
  let map f = List.map f
end

implicit module OptionFunctor = struct
  type 'a t = 'a option
  let map f = function
    | Some x  -> Some (f x)
    | None    -> None
end

In addition to these concrete instances, it is also possible to define instances inductively. For example by introducing an implicit module defining the functor interface for compositions or functors:

implicit module ComposeFunctor {A : FUNCTOR} {B : FUNCTOR } = struct
  type 'a t = ('a B.t) A.t
  let map f = A.map (B.map f)
end

That is to say, any two functors composed, for instance a list of options, is also a functor! Mapping is achieved by mapping over both the inner and the outer structures. Here are a couple of examples:

map String.length [Some "foo"; None; Some "bar"];;
map String.length (Some ["a"; "bc"; "def"]);;

Similarly, the product of two functors also forms a functor:

implicit module ProdFunctor {A : FUNCTOR} {B : FUNCTOR } = struct
  type 'a t = 'a A.t * 'a B.t
  let map f (x,y) = (A.map f x, B.map f y)
end

This enhances the universe of mappable structures further, for instance:

map String.length ([Some "apple"; None], Some "pear");;

The reason the examples above type check is because the machinery around modular implicits is able derive the functor by first considering the product functor and secondly the list composition functor with list and option for the first component and the option functor for the second. Without the implicitness one would have to manually do the plumbing, as in:

module F = ProdFunctor{ComposeFunctor{ListFunctor}{OptionFunctor}}{OptionFunctor}
map {F} String.length ([Some "apple"; None], Some "pear");;

What’s also worth noticing is that any variation of nested functors or product of functors are guaranteed by induction to be well behaved, that is satisfying the functor laws:

map (fun y -> y) x  =  x
map f (map g x)     = map (fun y -> f (g y)) x

This is of course conditional on having valid concrete functors at the bottom of the stack. For some more background on functors laws with OCaml examples, see this post.

There are some interesting applications of these concepts in the context of generic programming, a topic which I hope to come back to.

For now, here is the complete code from the examples above.