Mimic Java/Python class using OCaml modules

Hello everyone,
I am trying to mimic a Java class using a very simple example.
Could you please tell me if it is a correct (OCaml) way?

module Lamp = struct
  type state = On | Off
  type lamp_state = {mutable c: state}
  type lamp_color = {mutable cl: string}
  let set_color clr = {cl = clr}
  let create_lamp () = {c = Off}
  let turn_on s = s.c <- On
  let get_state s = match s.c with
    | On ->  "On"
    | Off -> "Off"
end
let lmp = Lamp.create_lamp ();;
Lamp.turn_on lmp;;
Lamp.get_state lmp;;

Mostly, but there is some mistakes. I would write it this way:

module Lamp = struct
  type state = On | Off

  (* this is a convention in OCaml to name `t' the principal type defined by your
      module and in your case you have two attributes, namely state and color *)
  type t = {mutable state : state; mutable color : string}

  let create c = {state = Off; color = c}
  let set_color lamp c = lamp.color <- c
  let turn_on lamp = lamp.state <- On 
  let get_state lamp = lamp.state
end

And if you prefer pure functional data structure without mutable attributes, you can do:

module Lamp = struct
  type state = On | Off

  type t = {state : state; color : string}

  let create c = {state = Off; color = c}
  let set_color lamp c = {lamp with color = c}
  let turn_on lamp = {lamp with state = On}
  let get_state lamp = lamp.state
end

To be honest your are not trying to mimic Java/Python class but the converse is true. This OCaml code is the right way to formalize your thought (this is really what you have in mind), and most of object oriented languages try to mimic this code, but do it stupidly because they don’t have a decent module system.

6 Likes

If you want, you can even add some additional sugar to @kantian’s solution:

module Lamp = struct
  type state = On | Off

  type t = {state : state; color : string}

  let create color = {state = Off; color}         (* Added sugar here ... *)
  let set_color lamp color = {lamp with color}    (* ... and here.        *)
  let turn_on lamp = {lamp with state = On}
  let get_state lamp = lamp.state
end

(Although it’s not clear the additional sugar makes the code any sweeter.)

As has been said by Alan Perlis, syntactic sugar can cause cancer of the semicolon.

One more sugar: type t = {state; color : string}

That doesn’t work for me in a 4.06.0 utop session - does OCaml support that syntax?

I believe that’s Reason-only syntax (for now?).

I simplified @kantian 's example by

module Lamp2 = struct
  type state = On | Off

  type t = {mutable state : state}

  let create = {state = Off}
  let turn_on lamp = lamp.state <- On
  let get_state lamp = lamp.state
end

And tested it by

utop # let la = Lamp2.create;;
val la : Lamp2.t = {Lamp2.state = Lamp2.Off}
# Lamp2.turn_on la;;
- : unit = ()
# let lb = Lamp2.create;;
val lb : Lamp2.t = {Lamp2.state = Lamp2.On}

Well, the state of lb is On

I made another example with a slight difference on adding () parameter for create

module Lamp3 = struct
  type state = On | Off

  type t = {mutable state : state}

  let create () = {state = Off}
  let turn_on lamp = lamp.state <- On
  let get_state lamp = lamp.state
end

And it works as expected:

# let l3a = Lamp3.create ();;
val l3a : Lamp3.t = {Lamp3.state = Lamp3.Off}
# Lamp3.turn_on l3a;;
- : unit = ()
# let l3b = Lamp3.create ();;
val l3b : Lamp3.t = {Lamp3.state = Lamp3.Off}

I was curious why the () would make it so different?

Because without it you do not define a function (which generates lamp) but a t value. Your Lamp2 module defines only a unique lamp so la, lb and Lamp2.create are only different aliases to the same value.

1 Like

@bcc32 Whoops, you’re right. Strangest thing, I could’ve sworn that worked in OCaml syntax.

If you go that way, we can further improve the API using optional and named arguments. :slight_smile:

 module Lamp = struct
  type state = On | Off

  type t = {state : state; color : string}

  let create ?(state=Off) color = {state; color}
  let set_color lamp ~color = 
    if lamp.color = color then lamp else {lamp with color}
  let get_color lamp = lamp.color
  let turn_on lamp = match lamp.state with
    | On -> lamp
    | Off -> {lamp with state = On}
  let turn_off lamp = match lamp.state with
    | On -> {lamp with state = Off}
    | Off -> lamp 
  let get_state lamp = lamp.state
end

Here the functions only allocate new values when needed. And from the module user point of view:

(* import Lamp.state to use On and Off without need to prefix by path name Lamp *)
type state = Lamp.state = On | Off

(* by default the lamp is Off *)
let lmp = Lamp.create "red";;
val lmp : Lamp.t = {Lamp.state = Lamp.Off; color = u"red"}

(* we can define a create_on function *)
let create_on = Lamp.create ~state:On;;
val create_on : string -> Lamp.t = <fun>

(* with it the lamp is On at creation *)
let lmp = create_on "yellow";;
val lmp : Lamp.t = {Lamp.state = Lamp.On; color = u"yellow"}

(* thanks to named arguments we can easily partially apply `set_color` with color parameter *)
let paint_blue = Lamp.set_color ~color:"blue";;
val paint_blue : Lamp.t -> Lamp.t = <fun>

paint_blue lmp;;
- : Lamp.t = {Lamp.state = Lamp.On; color = u"blue"}

(* or use the pipe operator conveniently *)
lmp |> Lamp.turn_off |> Lamp.set_color ~color:"orange";;
- : Lamp.t = {Lamp.state = Lamp.Off; color = u"orange"}

And of course, to have encapsulation we should add a signature coercion on the module Lamp to leave the type t abstract.

module Lamp : sig
  type t
  type state = On | Off
  val create : ?state:state -> string -> t
  val set_color : t -> color:string -> t
  val get_color : t -> string
  val turn_on : t -> t
  val turn_off : t -> t
  val get_state : t -> state
end = struct
  type state = On | Off

  type t = {state : state; color : string}

  let create ?(state=Off) color = {state; color}
  let set_color lamp ~color = 
    if lamp.color = color then lamp else {lamp with color}
  let get_color lamp = lamp.color
  let turn_on lamp = match lamp.state with
    | On -> lamp
    | Off -> {lamp with state = On}
  let turn_off lamp = match lamp.state with
    | On -> {lamp with state = Off}
    | Off -> lamp 
  let get_state lamp = lamp.state
end
1 Like

Thanks a lot to everybody for replies!

But I have another, related question.
Now I am trying to implement a doubly linked list, like it is done here in Python:

class Node(object):

    def __init__ (self, d, n = None, p = None):
        self.data = d
        self.next_node = n
        self.prev_node = p

    def get_next (self):
        return self.next_node

    def set_next (self, n):
        self.next_node = n

    def get_prev (self):
        return self.prev_node

    def set_prev (self, p):
        self.prev_node = p

    def get_data (self):
        return self.data

    def set_data (self, d):
        self.data = d

There is an implementation in OCaml on rosettacode:

type 'a dlink = {
  mutable data: 'a;
  mutable next: 'a dlink option;
  mutable prev: 'a dlink option;
}

My question is - how to specify this type without option keyword? So the next/previos can be either Nil or another Node/dlink?

Currently I came up to

type 'a node = Nil | Node of 'a * 'a node * 'a node;;
type 'a ddlist = {mutable next     : 'a node;
                        mutable previous : 'a node};;

but in this case in the Node(data, next, prev) , next and prev are not mutable.
How to work it around?

You could try:

type 'a dlist =
  | Nil
  | Node of 'a node
and 'a node = {
  mutable data : 'a;
  mutable next : 'a dlist;
  mutable prev : 'a dlist;
}
1 Like

You should have a look at the zipper data structure (here a possible implementation in OCaml). The data structure is very well named: when you close your coat with a zipper your are using a double linked list. :wink:

1 Like

Thanks !)
Yet another question …

module Lamp = struct 
    type 'a t = ...
end;;

module LampsContainer = struct 
    type t = {container:  (TYPE FROM LAMP) 'a t list}
end;;

How to refer to the type 'a t from the module Lamp from inside the module LampContainer?

That way should work:

module Lamp = struct 
    type 'a t = ...
end;;

module LampsContainer = struct 
    type 'a t = {container : 'a Lamp.t list}
    (* or  type t = {container : 'a. 'a Lamp.t list}, depending on what you want *)
end;;
1 Like