Functors which are not functors

The Ocaml module system

I've always been jealous of ML's module system

— Simon Peyton Jones

Elements of Ocaml syntax

Typing with a colon

i : int

Type argument order

(** Also the constructor for list is double-colon *)
( :: ) : 'a -> 'a list -> 'a list

Modules and signatures

module Fixed = struct
  
  let scale = 8

  let of_int i = i lsl scale
  let half = 1 lsl (scale - 1)

  (* note: not the same (+). *)
  let (+) = (+)
  let ( * ) n p = (n*p) asr scale 

end
module Fixed : sig
  
  (** [of_int i] is [i] as a fixed point
      number. *)
  val of_int : int -> int

  (** [half] is the number 1/2. *)
  val half : int

  (** [n+p] is the sum of [n] and [p], this
      operation is exact. *)
  val (+) : int -> int -> int

  (** [n*p] is the product of [n] and [p],
      rounded down. *)
  val ( * ) : int -> int -> int

end
let x = Fixed.half
open Fixed

let y = half * half + (Fixed.of_int 1)

Files as implicit modules

let scale = 8

let of_int i = i lsl scale
let half = 1 lsl (scale - 1)

(* note: not the same (+). *)
let (+) = (+)
let ( * ) n p = (n*p) asr scale 
(** [of_int i] is [i] as a fixed point
    number. *)
val of_int : int -> int

(** [half] is the number 1/2. *)
val half : int

(** [n+p] is the sum of [n] and [p], this
    operation is exact. *)
val (+) : int -> int -> int

(** [n*p] is the product of [n] and [p],
    rounded down. *)
val ( * ) : int -> int -> int

fixed.mli

fixed.ml

let x = Fixed.half
open Fixed

let y = half * half + (Fixed.of_int 1)

Submodules

let scale = 8

let of_int i = i lsl scale
let half = 1 lsl (scale - 1)

(* note: not the same (+). *)
let (+) = (+)
let ( * ) n p = (n*p) asr scale 

module Repr = struct

  let integer_part n = n asr scale

  let fractional_part n =
    n land (lnot (-1 lsl scale))

end
val of_int : int -> int

val half : int

val (+) : int -> int -> int

val ( * ) : int -> int -> int

module Repr : sig

  (** [integer_part n] is the integer part
      of the fixed point number [n]. *)
  val integer_part : int -> int
  
  (** [fractional_part n] is the fractional
      part of the fixed point number [n]
      multiplied by 2^8.*)
  val fractional_part : int -> int

end

fixed.mli

fixed.ml

let x =
  Fixed.Repr.integer_part (Fixed.of_int 57)
open Fixed

let y = Repr.fractional_part (of_int 18)
open Fixed.Repr
let y = integral_part (Fixed.of_int 42)

Abstract types

type t = int

let scale = 8

let of_int i = i lsl scale
let half = 1 lsl (scale - 1)

(* note: not the same (+). *)
let (+) = (+)
let ( * ) n p = (n*p) asr scale 
(** Fixed point numbers with scaling
    factor 2^8*)
type t

(** [of_int i] is [i] as a fixed point
    number. *)
val of_int : int -> t

(** [half] is the number 1/2. *)
val half : t

(** [n+p] is the sum of [n] and [p], this
    operation is exact. *)
val (+) : t -> t -> t

(** [n*p] is the product of [n] and [p],
    rounded down. *)
val ( * ) : t -> t -> t

fixed.mli

fixed.ml

let x : Fixed.t = Fixed.half
open Fixed

let y = half * half + (Fixed.of_int 1)

Local opens (=import)

(** Fixed point numbers with scaling
    factor 2^8*)
type t

(** [of_int i] is [i] as a fixed point
    number. *)
val of_int : int -> t

(** [half] is the number 1/2. *)
val half : t

(** [n+p] is the sum of [n] and [p], this
    operation is exact. *)
val (+) : t -> t -> t

(** [n*p] is the product of [n] and [p],
    rounded down. *)
val ( * ) : t -> t -> t

fixed.mli

let y =
  Fixed.(+)
    (Fixed.(*)
      Fixed.half
      Fixed.half)
    (Fixed.of_int 1)
let y =
  Fixed.(half * half + (Fixed.of_int 1))

(* equivalently *)

let y =
  let open Fixed in
  half * half + (Fixed.of_int 1)

vs.

Functors

module type Scale = sig

  (** Scaling factor for fixed point numbers
      expressed as a binary logarithm. *)
  val scale : int

end

module Make (S : Scale) : sig

  (** Fixed point numbers with scaling
      factor 2^8*)
  type t
  
  val of_int : int -> t
  val half : t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t

end

fixed.ml

fixed.mli

So, I hear you like dependent types

module type Scale = sig

  val scale : int

end

module Make (S : Scale) = struct

  type t = int
  
  let of_int i = i lsl S.scale
  let half = 1 lsl (S.scale - 1)
  
  let (+) = (+)
  let ( * ) n p = (n*p) asr S.scale 

end
module Fixed8 =
  Fixed.Make(struct scale = 8 end)


let y =
  Fixed8.(half * half + (Fixed8.of_int 1))

More functors

module type Integer = sig

  type t
  val of_int : int -> t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t
  val (lsl) : t -> int -> t
  val (asr) : t -> int -> t
  …

  module Carry : sig

    val plus : t -> t -> bool*t
    val times : t -> t -> t*t

  end

end

module type Scale = sig

  val scale : int

end
module Fixed (I : Integer) (S : Scale) : sig

  (** Fixed point numbers with scaling
      factor 2^8*)
  type t

  val of_int : int -> t
  val half : t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t

end
module type Integer = …
module type Scale = …

module Fixed (I : Integer) (S : Scale) : sig

  (** Fixed point numbers with scaling
      factor 2^8*)
  type t

  val of_int : int -> t
  val half : t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t

end
module Int : Integer
module DoubleInt (I : Integer) : Integer

module Fixed16 =
  Fixed.Make
    (DoubleInt(Int))
    (struct scale = 16 end)


let y =
  Fixed16.(half * half + (Fixed16.of_int 1))
(** Almost complete implementation *)

module type Integer = sig

  type t
  val of_int : int -> t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t
  val (lsl) : t -> int -> t
  val (asr) : t -> int -> t
  …

  module Carry : sig

    val plus : t -> t -> bool*t
    val times : t -> t -> t*t

  end

end

module type Scale = sig

  val scale : int

end

module Int : Integer = struct

  type t = int

  let of_int i = i
  let (+) = (+)
  let ( * ) = ( * )
  let (lsl) = (lsl)
  let (asr) = (asr)

  module Carry = struct

    let plus n p = …
    let times n p = …

  end

end

module DoubleInt (I : Integer) : Integer = struct

  type t = I.t * I.t
  let of_int i = I.(of_int 0, of_int i)

  module Carry = struct

    let plus (nh,nl) (ph,pl) =
      let (carryl,low) = I.Carry.plus nl pl in
      let (carryh,high) =
        if carryl then
          I.Carry.plus nh ph
        else
          let (carry,h) = I.Carry.plus nh ph in
          (carry, I.(h + of_int 1))
      in
      (carryh, (high,low))

    let (+) n p = snd (plus n p)

    (* Karatsuba multiplication. *)
    let times (nh,nl) (ph,pl) =
      let h = I.Carry.times nh ph in
      let l = I.Carry.times nl pl in
      let (mc,(mh, ml)) =
        plus
          (I.Carry.times nh pl)
          (I.Carry.times nl ph)
      in
      let mcw = I.of_int (if mc then 1 else 0) in
      let (c,rl) = plus (ml,I.of_int 0) l in
      let rh =
        if c then
          h + (mh, mcw)
        else
          h + (mh, mcw) + of_int 1
      in
      ( rh , rl )

  end

  let (+) = Carry.(+)
  let ( * ) n p = snd (Carry.times n p)
  let (lsl) (nh,nl) k = …
  let (asr) (nh,nl) k = …
  …

end

module Fixed (I : Integer) (S : Scale) : sig

  (** Fixed point numbers with scaling
      factor 2^8*)
  type t

  val of_int : int -> t
  val half : t
  val (+) : t -> t -> t
  val ( * ) : t -> t -> t

end = struct

  type t = I.t

  let of_int i = I.(of_int i lsl S.scale)
  let half = I.(of_int 1 lsl (S.scale - 1))

  let (+) = I.(+)
  let ( * ) n p = I.((n*p) asr S.scale)

end

More

  • Manifest dependent types (with)
  • (Mutually) recursive modules
  • First-class modules
  • Private types
  • Generative exceptions/constructors

Modules in Haskell

Research

Backpack: system of mixins at the library level

In 8.4

Functors which are not functors

By Arnaud Spiwack

Functors which are not functors

The ML family of programming languages have, basically forever, come with a sweet module system (including functors which are not, well, functors, in the mathematical sense; no, seriously, they are completely unrelated, except for the name). I want to run you through a few feature of Ocaml's module system. This includes the aforementioned functors, first-class modules, as well as local namespace imports, which I really love. And why I miss them when I program in Haskell. I'll start with a typology of module and namespace systems. And we'll touch on how differently type-classes and ML functors solve the same problem.

  • 833