A Primer on Functors

Programming applications in Irmin requires a familiarity with OCaml's functors. Here are a few resources for learning more about them:

  • The OCaml manual: The definitive guide to different aspects of the OCaml language.

What follows is an Irmin-specific introduction to functors.

Generalisation

Functors, in a non-theoretical sense, can be thought of as functions over modules. A functor takes a module and produces another module. For example, we might produce a new module that hashes values provided they can be serialised to a string.

(* A module signature for modules who have a main type called [t]
   and that provide a function [serialise] to convert [t] to a [string]. *)
module type Serialisable = sig
    type t
    val serialise : t -> string
end

We will also need another module signature describing what a digestable type looks like.

module type Digestable = sig
    type t
    (** The values to digest *)

    type hash
    (** The type of digests produced *)

    val hash_to_string : hash -> string
    (** Convert digests to a string *)

    val digest : t -> hash
end

We could then provide a SHA256 hashing functor for serialisable types.

module SHA256 (S : Serialisable) : Digestable with type t = S.t and type hash = Digestif.SHA256.t = struct
    type t = S.t
    type hash = Digestif.SHA256.t
    let hash_to_string = Digestif.SHA256.to_raw_string
    let digest v = Digestif.SHA256.digest_string (S.serialise v)
end

We can then use our functor.

module Integer = struct
  type t = int
  let serialise = string_of_int
end

module Digest_int = SHA256(Integer)

And then use it.

# let d = Digest_int.digest 42;;
val d : Digest_int.hash = <abstr>
# Digest_int.hash_to_string d |> Base64.encode_exn;;
- : string = "c0dctApWjo2ooEXO0RATfhWfiQrE2og7axfcZRs6gEk="