OO with rere? how to work-around?

The kind of polymorphism you’re looking at is what functors are made for.

The solution proposed by @dbuenzli is a real OO one. :wink: The type he defined with an existential GADT is equivalent to an object type.

I will explain with your FOE example. A foe is something that has two methods f and p, and you have two kinds of foe: Foe1.t and Foe2.t. At first you are looking for a type that contains these two kinds, because you can’t built this list [Foe1.make (); Foe2.make ()] for typing reason. You could use a variant but you’ll have problems if you want to add more kinds of foe. For instance, you may want to add the kinds foe3, foe4 and so on. But since there is infinitely many such kinds, you will end up with an infinite sum, and the limit of this infinite sum is precisely the GADT defined by @dbuenzli (which is equivalent to an object in OOP). This question has been discuss, for instance, here and here.

Basically, what I will do is something like this.

module type Foe = sig
  (* a foe is something that satisfy this interface *)
  module type S = sig
    type t
    val f : t -> t
    val p : t -> unit
  end

  (* the methods, as in OOP, of a foe of kind 'a *)
  type 'a meth = (module S with type t = 'a)

  (* the type of all foes of any kinds *)
  type t

  (* a generic constructor for foe values *)
  val make : 'a meth -> 'a -> t

  (* all foes are foes, id est the type `t`
     satisfy its own interface S *)
  val f : t -> t
  val p : t -> unit
end

For the concrete implementation of the type Foe.t you can choose objects, record of closures or the GADT. Personnaly, I prefer the GADT. And now you can easily define the list you were looking for:

let foe1 = Foe1.make ()
let foe2 = Foe2.make ()

let l = [
  Foe.make (module Foe1) foe1;
  Foe.make (module Foe2) foe2;
]

And if you want to write (ad-hoc) polymorphic code over all these kind of foes, you could write plain functors or first-class ones with first-class modules.

(* with functors *)
module F (M : Foe.S) = struct
  let foo l = List.map M.f l
  let bar l = List.iter M.p l
end

(* with first-class functors *)
let foo (type a) (m : a Foe.meth) l = 
  let module M = (val m) in
  List.map M.f l

let bar (type a) (m : a Foe.meth) l =
  let module M = (val m) in
  List.iter M.p l

And you can use, for instance, the first-class version with any kind of foe list:

foo (module Foe1) [foe1; foe1]
bar (module Foe2) [foe2; foe2]
foo (module Foe) l

This technique is described in the chapter about first-class module in RWO (they even give another concrete implementation of the type Foe.t using modules and abstract type).

Edit: If you don’t want to write functors (be they first-class or not) and do ad-hoc polymorphism as with an OOP style, you can juste write function that consume Foe.t and only use the two methods Foe.f and Foe.p (that’s exactly what you will do if you put all your different kinds of foe in only one class in OOP), but you will loose some typing information.

3 Likes