OO with rere? how to work-around?

Hi, I’m trying to find a way to go further with a mini-game project, with rescript version 8.4.2 (using its ocaml syntax 4.06.1).

I reached a point where I need ( / want) to define several kind of enemies (not only one) and I’m wondering how to do so. Using OO would have been just perfect for the task, so that every enemy share the same interface, and the implementations are different and hidden inside each objects.

Unfortunately objects don’t work very well in ReScript 8.4.2. It just doesn’t work if there’s an initialiser, and other elements. I found a work-around which is to define an object with default values, and methods to change these default, in a new_oo_foe function which has the role to initialise my object.

This is what you can see in directory 3, I define the type type oo_foe, there is the default object dummy_oo_foe (), and the initialiser new_oo_foe, you can check that this work-around does work, but I’m not really happy with this work-around.

In directory 5 you can see that I came back to a record and which I’m trying use in a fake OO style with a field supposed to be used as a method (see type foe and its method foe_gun).

But the problem is that I can change the method implementation, but I would also need to change the fields of the record to match different implementations, which we can do with OO and private variables, but not with a record.

Could also use a variant to join several types in one, but it’s also not as elegant as a real OO.

Is there someone that would have a solution (or work-around) to suggest?

You can try to use first class modules. This allows you to conveniently use modules and all the datatypes you may want to define them.

Here’s something to get you started:

type game_state
type foe_bullet

module Foe : sig

  (** The type for foe implementations. *)
  module type T = sig
    type t
    val gun : t -> game_state -> int -> t * foe_bullet list
  end

  type t
  val make : (module T with type t = 'a) -> 'a -> t
  val gun : t -> game_state -> int -> t * foe_bullet list
end = struct

  module type T = sig
    type t
    val gun : t -> game_state -> int -> t * foe_bullet list
  end

  type t = V : (module T with type t = 'a) * 'a -> t

  let make m foe = V (m, foe)
  let gun (V ((module F) as m, foe)) st i =
    let foe', bullets = F.gun foe st i in
    make m foe', bullets
end

Thanks for replying Daniel,

  1. It looks complicated, I usually like to keep my code readable for an early beginner. (But considering that I also don’t like the other solutions.) This code is compatible with rescript 8.4.2, thanks.

  2. I can’t find the documentation for this in the official manual for 4.14, so I don’t know how to go further with it.

Is there a way to make work something more simple like this?

module type FOE =
  sig
    type t
    val make : unit -> t
    val f : t -> t
    val p : t -> unit
  end

module Foe1 : FOE = struct
  type t = { a: int; b: int; }
  let make () = { a=1; b=1; }
  let f v = { v with a = succ v.a }
  let p v = Printf.printf " { %d %d }\n" v.a v.b
end

module Foe2 : FOE = struct
  type t = { a: int; b: int; c: int; }
  let make () = { a=1; b=1; c=1; }
  let f v = { v with c = succ v.c }
  let p v = Printf.printf " { %d %d %d }\n" v.a v.b v.c
end

let () =
  let xs = [ Foe1.make (); Foe2.make () ] in
  ignore xs

Two features are being used first class modules and existential datatypes via generalized algebraic datatypes.

If you want something simpler you could simply define a record of functions, each kind of foe can then define it’s behaviour by defining the functions that access a common closure environment in which you have foe specific parameters.

Something like this:

module Foe : sig
  type t 
  type behaviour = { gun : t -> game_state -> int -> t * foe_bullet list }
  val make : behaviour -> t
  val gun : t -> game_state -> int -> t * foe_bullet list
end = struct

  type t = { behaviour : behaviour }
  and behaviour = { gun : t -> game_state -> int -> t * foe_bullet list }

  let make behaviour = { behaviour }
  let gun ({ behaviour } as foe) st i = behaviour.gun foe st i
end

make_foe1 ~x ~y = 
  let gun foe st i = … in 
  { Foe.gun }

There is a fairly simple idiomatic way in OCaml to handle what class hierarchies handle in OO languages: variants.

E.g.,

type foe_base = { a : int; b : int}

type foe =
| Foe1 of foe_base
| Foe2 of { base : foe_base; c : int }

let foe1 () = Foe1 { a = 1; b = 1 }
let foe2 () = Foe2 { base = { a = 1; b = 1 }; c = 1 }

let f = function
  | Foe1 foe_base -> Foe1 { foe_base with a = succ foe_base.a }
  | Foe2 { base; c } -> Foe2 { base; c = succ c }

let p = function...

let () =
  let xs = [foe1 (); foe2 ()] in
  ignore xs

Of course it’s at the other end of the ‘expression problem’, so it works if the different types of foes should not be extensible by users of the library. In this case I think it works because it’s an application (game) so there would be no need for third parties to extend the foe types.

1 Like

Hi Yawar, thanks for replying,

I was not seeking after OO for its hierarchy feature, but for its polymorphism, in order to put all my enemies of different kind in a single list. But the variant makes this possible, I was also giving an example of this at the end of my first message. In my case there are not too many methods that would need to be wrapped this way, but this is still more verbose than a real polymorphic OO.

It was not possible in the past, but it’s now possible to extend variants.

I will also like if I can make the base of my application as a lib. It would be a lib to build shmups games of course. If you look at the git repos for this mini-project (sdl2, re) you may notice that I’m not really working in a way that programmers usually work, I made shmup_av first that is a shmup that is minimal and which works in a traditional way, and I use it as a base for other experimentations, and skins. I made product-design studies and this is how I learned to work on a product project. We usually make many different prototypes, not only one or two.

So I would also be happy if experimentations could be easier to transfer from one attempt to another. A real OO would also make this easier.

1 Like

A link to this in the module chapter would be nice.

This looks similar than the attempt that I made in directory 5 (I gave the link in my first message), but the problem with this solution is that you can only provide different methods, but you can not change the variables as we would do with different private variables in a real object.

But when you said “closure environment” it made me realise that I can hide private variables by partial application of the functions of the record.

type foe = {
  a: int;
  f: foe -> foe;
  print: foe -> unit;
}

let make_foe1 () =
  let b = 2 in
  let c = 3 in
  let rec f =
    (fun b c this ->
      let b = succ b in
      { this with
        a = succ this.a;
        f = f b c;
      }
    )
  in
  { a = 1;
    f = f b c;
    print = (fun this -> Printf.printf " { %d }\n" this.a);
  }

let () =
  let x = make_foe1 () in
  x.print x;
  let x = x.f x in
  x.print x;
;;

But it also looks like a work around, not as a real solution.

PS: Does someone know if the authors of melange are planning to make oo work?

FYI: for a game engine, it may seem like OOP is a good match at first, but it quickly becomes insufficient. The reason is that you will want to be able to expand your entities outside of the code (via some configuration language) as the game grows, and OOP is simply unable to deal with dynamic hierarchies. What you really need is an inheritance-enabled database. OOP hierarchies are only viable for simple toy games or for games without many entities with similar behavior (which you will inevitably want to extend outside of the code), and for these limited applications, it’s just as useful to use variants.

1 Like

Yes, I also made an attempt with entity/component-system in 2012, but I think it’s over-engineering for something simple like a shmup. I think ECS fits better for something like a RTS game.

Also a problem with my attempt with ECS was that it was slow, and didn’t scale very well for many entities, because you need to put the component in a structure and retrieve it in a way that will be slower than accessing the field of a record or an object, and also the variable of the component has to be wrapped in a variant to be polymorphic and it also has a high cost when all the variables of all the entities are like that.

I don’t know how to make a good and efficient implementation of ECS in OCaml. But here considering that I’m using rescript to produce an HTML/JS mini-game, I could maybe just use a ECS engine available in Javascript. I just see that there are several. Adding any fields to a variable and putting anything of any type in it, is something more natural in JS than in ocaml.

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

Hi,
Thanks for your time, thanks for the time you spent, I read the documentation and the links, but I haven’t been able to expand the (incomplete) example to make it work.

I finally came to think a simpler solution could be to aggregate all the fields required for every different kind of entities in a single generic record, and just feed unneeded fields with unused values.

Yes, this solution look like stupid compared to the GATD, but it’s KISS (keep it simple).

I was waiting for first class modules since a while, but it doesn’t look as great as what I expected.

Can’t do:

let (module M) = if true then (module M1) else (module M2) in

What is called “first class modules” seem more like a record with functions fields than a real first class module. I can not define a module type, and join in a single list several modules that all match this type, because when the implementations of an abstract type are different, it becomes impossible to join these different modules in a single structure.

The syntax is also more complex than it could be.

I finally came to think the same for creating an efficient ECS in ocaml. Just using a generic record with all the fields for all the different kind of entities, and feeding the uneeded fields with unused value (boxed values will just be shared). Just a little bit more space, but I think this kind of solution would scale better. And we could associate a bitfield that tells which field is used, that the systems of the ECS could quickly check to map the entities.

Did you try to add the module types? E.g., something like

let _whatever = 
  let (module M) = 
    if true then (module M1 : MY_SIG) else (module M2 : MY_SIG) in
  M.foo x y z
...

I did try it with ReScript just to be sure, and it works there as in OCaml. Note that it was ReScript 9.1.4 rather than your 8.4.2.

If you did this it means that you also need the subtyping feature of object system, and not only its ad-hoc polymorphism one. But there is also subtyping with module : just use the methods you need in your functor, and even if a module provide more methods you could pass it as an argument to your functor.

I’m not really sure we have the same notion of simplicity. How would you construct a value of this generic record type?

For sure you can, with a type annotation.

(* a module type *)
module type S = sig type t val e : t end

(* two instances *)
module A = struct type t = int let e = 1 end
module B = struct type t = string let e = "OCaml" end

(* your case with the right type annotation *)
let foo b : (module S) = if b then (module A) else (module B);;
val foo : bool -> (module S) = <fun>

Indeed, but that’s because modules are just records, this is not particular to first class module. First class module is just the possibility to treat modules as records, because that’s what they are. :wink:

(* this two types have exactly the same runtime representation *)
type 'a fcm = (module S with type t = 'a)
type 'a t = {e : 'a}

let m = {e = 1};;
val m : int t = {e = 1}

let cast (v : 'a t) : 'a fcm = Obj.magic v;;
val cast : 'a t -> 'a fcm = <fun>

include (val (cast m));;
type t = int
val e : t = 1

The utility of first-class module is to manipulate compilation units (which belong to the module language) as first-class value of the core language: to construct a record-like value from a compilation unit you just have to write (module A). And since there is subtyping in the module language, you get records with subtyping.

For sure you can, but I don’t see why you would want to do that. With the modules above:

let l : (module S) list = [(module A); (module B)];;
val l : (module S) list = [<module>; <module>]

The syntax could indeed have been simpler, but you can’t avoid some type annotations due to the more complex type system of the module language.

3 Likes

Yes, this is what I mean. We can’t without a type annotation.

Thanks a lot for this example. I was not able to find by myself how to do it.

It’s easier to see how to do with an example. Thanks a lot.

But now again, I’m not able to find by myself, how to get back one of these modules, and for example access the field e from your example.

Ah, ok. I see.
I can easily understand that this kind of implementation is probably not that easy to do.

Like below. I think it’s quite simple, and that maybe an early beginner can read it, understand it, and modify it.

Here below the solution I’m planning to use, please tell me what you think about it and comment.


type entity = {
  a: int;
  b: float;
  c: int * int;
}

let dummy = {
  a = 0;
  b = 0.0;
  c = (0, 0);
}

let make_entity_a () =
  { dummy with a = 3 }

let make_entity_b () =
  { dummy with b = 4.0 }

let make_entity_c () =
  { dummy with c = (10, 20) }

let () =
  let a = make_entity_a () in
  let b = make_entity_b () in
  let c = make_entity_c () in
  let my_entities = [ a; b; c ] in
  ignore (my_entities)

$ ocaml
OCaml version 4.14.0

# module type S = sig type t val e : t end ;;
module type S = sig type t val e : t end
# module A = struct type t = int let e = 1 end ;;
module A : sig type t = int val e : int end
# module B = struct type t = string let e = "OCaml" end ;;
module B : sig type t = string val e : string end
# let foo b : (module S) = if b then (module A) else (module B) ;;
val foo : bool -> (module S) = <fun>
# foo true ;;
- : (module S) = <module>
# [ foo true; foo false ] ;;
- : (module S) list = [<module>; <module>]
# [| foo true; foo false |] ;;
- : (module S) array = [|<module>; <module>|]
# let ms = [| foo true; foo false |] ;;
val ms : (module S) array = [|<module>; <module>|]
# (module ms.(0)).e ;;
Error: Syntax error: module-expr expected.
# ms.(0).e ;;
Error: This expression has type (module S) which is not a record type.
# let (module R) = ms.(0) ;;
Error: Modules are not allowed in this pattern.
#

It’s a bit late in France, so I’ll give you a more complete answer tomorrow. But, for your error, when you want to access the fields of a first class module, you have to unpack it in the module language with the (val m) construct:

let module R = (val ms.(0)) in R.e;;
Line 1, characters 31-34:
Error: This expression has type R.t but an expression was expected of type 'a
       The type constructor R.t would escape its scope

(* or equivalently *)
let (module R) = ms.(0) in R.e;;
Line 1, characters 27-30:
Error: This expression has type R.t but an expression was expected of type 'a
       The type constructor R.t would escape its scope

And, as you can see, you can’t access the field e in this case because of your (module S) constraint (that’s why I didn’t see why you wanted to do this). It’s as if you wanted to access private field of an object. Your array ms is equivalent to this one with object:

let ms =[|object val e = 1 end; object val e = "OCaml" end|];;
val ms : <  > array = [|<obj>; <obj>|]

Without methods, you can’t do anything.

You began this series of postings with a question about emulating OO in ocaml. You indicated in the course of the exchanges that you were interested in the polymorphic aspect of OO rather than with the sub-typing used to achieve it in languages such as C++. In your latest posts you have explored the use of first-class modules to carry out the emulation. (If sub-typing is important to you then you need to look at polymorphic variants and/or ocaml objects/classes.)

OO in the sense to which you have referred requires data and methods to operate on the data. The simplest way to emulate that kind of polymorphic behaviour is with records holding closures, but the closures can equally well be held in a first-class module. Taking the simplest possible example I could envisage, which is the printing of different forms of data polymorphically to the console (below a string and an int, although it could be anything), you end up with something like this:

module type PRINTABLE = sig
  val print : unit -> unit
end

let print_instance (module M : PRINTABLE) =
  M.print ()

let of_string s =
  (module struct
     let print () = print_endline s 
   end : PRINTABLE)

let of_int i =
  (module struct
     let print () = print_int i ; print_newline ()
   end : PRINTABLE)

You wanted to put these polymorphic entities in a list or array, which you can do as follows:

let () =
  List.iter print_instance [ of_string "Hello" ; of_int 42 ]

The problem with this for more complicated situations is that if you have multiple methods for the same data, each method keeps its own copy of the data as a closure. That’s only a pointer but it can add up. There are two common ways to deal with this. The first is to use a wrapper module, to which a convenience ‘make’ function can be added. The following example still only has a single ‘print’ method for the datum ‘self’, but there could be multiple methods:

module type PRINTABLE = sig
  type t
  type u
  val print : t -> unit
  val make: u -> t
end

module type INSTANCE = sig
  module Printable : PRINTABLE
  val self : Printable.t
end
 
let print_instance (module M : INSTANCE) =
  M.Printable.print M.self

module PrintString = struct
  type t = string
  type u = string
  let print s = print_endline s
  let make s = s
end

module PrintInt = struct
  type t = int
  type u = int
  let print i = print_int i ; print_newline ()
  let make i = i
end

let of_string u =
  (module struct
     module Printable = PrintString
     let self = PrintString.make u
   end : INSTANCE)

let of_int u =
  (module struct
     module Printable = PrintInt
     let self = PrintInt.make u
   end : INSTANCE)

let () =
  List.iter print_instance [ of_string "Hello" ; of_int 42 ]

A second way is to use GADTs for the same purpose:

module type PRINTABLE = sig
  type t
  type u
  val print : t -> unit
  val make: u -> t
end

type instance = Instance : (module PRINTABLE with type t = 'a) * 'a
                           -> instance

let print_instance (Instance ((module M), t)) = M.print t

module PrintString = struct
  type t = string
  type u = string
  let print s = print_endline s
  let make s = s
end

module PrintInt = struct
  type t = int
  type u = int
  let print i = print_int i ; print_newline ()
  let make i = i
end

let of_string u =
  Instance ((module PrintString), PrintString.make u)

let of_int u =
  Instance ((module PrintInt), PrintInt.make u)

let () =
  List.iter print_instance [ of_string "Hello" ; of_int 42 ]

I don’t know whether this is exactly what you are looking for - there are many variations on this theme, but I hope it helps.

Thank you for the answer.

Your sample provides a first step. In a game initialising an entity is a first step, we also need to update the state of entities at every game loop.

I tried to add an update method based on your print method, but I get the following error:

module type PRINTABLE = sig
  type t
  type u
  val print : t -> unit
  val make: u -> t
  val update: t -> t
end

type instance =
  Instance : (module PRINTABLE with type t = 'a) * 'a -> instance

let print_instance (Instance ((module M), t)) = M.print t
let update_instance (Instance ((module M), t)) = M.update t

module PrintString = struct
  type t = string
  type u = string
  let print s = print_endline s
  let make s = s
  let update s = s ^ s
end

module PrintInt = struct
  type t = int
  type u = int
  let print i = print_int i ; print_newline ()
  let make i = i
  let update i = i + i
end

let of_string u =
  Instance ((module PrintString), PrintString.make u)

let of_int u =
  Instance ((module PrintInt), PrintInt.make u)

let () =
  let entities = [ of_string "Hello" ; of_int 42 ] in
  let entities = List.map update_instance entities in
  List.iter print_instance entities

Error message:

$ ocaml c2.ml 
File "./c2.ml", line 13, characters 49-59:
13 | let update_instance (Instance ((module M), t)) = M.update t
                                                      ^^^^^^^^^^
Error: This expression has type $Instance_'a
       but an expression was expected of type 'a
       The type constructor $Instance_'a would escape its scope

Same for the other sample:

module type PRINTABLE = sig
  type t
  type u
  val make: u -> t
  val print : t -> unit
  val update : t -> t
end

module type INSTANCE = sig
  module Printable : PRINTABLE
  val self : Printable.t
end

let print_instance (module M : INSTANCE) =
  M.Printable.print M.self

let update_instance (module M : INSTANCE) =
  M.Printable.update M.self

module PrintString = struct
  type t = string
  type u = string
  let make s = s
  let print s = print_endline s
  let update i = i ^ i
end

module PrintInt = struct
  type t = int
  type u = int
  let make i = i
  let print i = print_int i ; print_newline ()
  let update i = i + i
end

let of_string u =
  (module struct
     module Printable = PrintString
     let self = PrintString.make u
   end : INSTANCE)

let of_int u =
  (module struct
     module Printable = PrintInt
     let self = PrintInt.make u
   end : INSTANCE)

let () =
  let entities = [ of_string "Hello"; of_int 42 ] in
  let entities = List.map update_instance entities in
  List.iter print_instance entities

Error:

$ ocaml c3.ml
File "./c3.ml", line 18, characters 2-27:
18 |   M.Printable.update M.self
       ^^^^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type M.Printable.t
       but an expression was expected of type 'a
       The type constructor M.Printable.t would escape its scope

I still think my idea with a record is more simple.

Yes, there is a little bit of space waste, but I think it’s not that much.

Compiled with the ocaml compiler, every unused field will not waste more than one word, either if its content is boxed or unboxed, because the dummy boxed content will be shared.

I noticed that it was not shared with the rescript output with the first version I gave above, but with only a small modification I also get something shared, as below:

type entity = {
  a: int;
  b: float;
  c: int * int;
}

let a = 0
let b = 0.0
let c = (0, 0)

let dummy = { a; b; c; }

let make_entity_a () =
  { dummy with a = 3 }

let make_entity_b () =
  { dummy with b = 4.0 }

let make_entity_c () =
  { dummy with c = (10, 20) }

let () =
  let a = make_entity_a () in
  let b = make_entity_b () in
  let c = make_entity_c () in
  let my_entities = [ a; b; c ] in
  ignore (my_entities)