Search code examples
f#ocamlfunctor

How to write code in F# for what functors do in OCaml?


I have many programs written in OCaml, some of them use functors. Now, I am considering of writing and re-writing a part of code in F# (to benefit some advantages that OCaml does not have). One thing I am afraid of is to write code in F# for what functors do in OCaml.

For instance, how could we emulate this example from OCaml manual in F#?

type comparison = Less | Equal | Greater

module type ORDERED_TYPE = sig
  type t
  val compare: t -> t -> comparison
end

module Set =
functor (Elt: ORDERED_TYPE) -> struct
    type element = Elt.t
    type set = element list
    let empty = []
    let rec add x s =
      match s with
        [] -> [x]
      | hd::tl ->
         match Elt.compare x hd with
           Equal   -> s         (* x is already in s *)
         | Less    -> x :: s    (* x is smaller than all elements of s *)
         | Greater -> hd :: add x tl
  end

module OrderedString = struct
  type t = string
  let compare x y = if x = y then Equal else if x < y then Less else Greater
end

module OrderedInt = struct
  type t = int
  let compare x y = if x = y then Equal else if x < y then Less else Greater
end

module StringSet = Set(OrderedString)
module IntSet = Set(OrderedInt)

let try1 () = StringSet.add "foo" StringSet.empty
let try2 () = IntSet.add 2 IntSet.empty

Solution

  • As you noticed, F# doesn't have functors - F# modules cannot be parameterized by types. You can get similar results in F# using the object oriented parts of the language - interfaces, generic classes and inheritance.

    Here's a heavy handed approach at emulating your example.

    type Comparison = Less | Equal | Greater
    
    /// Interface corresponding to ORDERED_TYPE signature
    type IOrderedType<'a> = 
        abstract Value: 'a
        abstract Compare: IOrderedType<'a> -> Comparison
    
    /// Type that implements ORDERED_TYPE signature, different instantiations
    /// of this type correspond to your OrderedInt/OrderedString modules.
    /// The 't: comparison constraint comes from the fact that (<) operator 
    /// is used in the body of Compare.
    type Ordered<'t when 't: comparison> (t: 't) =
        interface IOrderedType<'t> with
            member this.Value = t
            member this.Compare (other: IOrderedType<'t>) = 
                if t = other.Value then Equal else if t < other.Value then Less else Greater
    
    /// A generic type that works over instances of IOrderedType interface.
    type Set<'t, 'ot when 't: comparison and 'ot :> IOrderedType<'t>> (coll: IOrderedType<'t> list) =
    
        member this.Values = 
            coll |> List.map (fun x -> x.Value)
    
        member this.Add(x: 't) = 
            let rec add (x: IOrderedType<'t>) s = 
                match coll with
                | [] -> [x]
                | hd::tl ->
                    match x.Compare(hd) with
                    | Equal   -> s         (* x is already in s *)
                    | Less    -> x :: s    (* x is smaller than all elements of s *)
                    | Greater -> hd :: add x tl
            Set<'t, 'ot>(add (Ordered(x)) coll)
    
        static member Empty = Set<'t, 'ot>(List.empty)
    
    /// A helper function for Set.Add. Useful in pipelines. 
    module Set =     
        let add x (s: Set<_,_>) =
           s.Add(x)
    
    /// Type aliases for different instantiations of Set 
    /// (these could have easily been subtypes of Set as well)
    type StringSet = Set<string, Ordered<string>>
    type IntSet = Set<int, Ordered<int>>
    
    let try1 () = Set.add "foo" StringSet.Empty
    let try2 () = Set.add 2 IntSet.Empty
    
    try1().Values
    try2().Values