The shape design problem

For a practical use-case here, I would try to resist the temptation to reach for the big
guns — GADTS, first-class-modules, polymorphic variants etc.

Simple records come a long way. First, defining a Shape module (the type t
can be made abstract):

module Shape = struct
  type t = { area : float ; draw : unit -> unit }
  let make ~area ~draw = { area; draw }
  let draw_all = List.iter (fun s -> s.draw ())
end

Second, define the Shape instances as separate modules where each module exposes
a to_shape function:

module Point = struct
  type t = { x : float; y : float}
  let make ~x ~y = { x; y }
  let to_shape { x; y } =
    Shape.make ~area:0. ~draw:(fun () -> Printf.printf "Point (%f,%f)" x y)
end

module Rectangle = struct
  type t = { bottom_left : Point.t; bottom_right : Point.t }
  let make ~bottom_left ~bottom_right = { bottom_left; bottom_right }
  let to_shape { bottom_left; bottom_right } = failwith "TODO"
end

To create some shapes and bundle them together:

let my_point = Point.make ~x:10. ~y:20.

let my_rectangle =
  Rectangle.make
    ~bottom_left:(Point.make ~x:1. ~y:1.)
    ~bottom_right:(Point.make ~x:4. ~y:5.)

let my_shapes = [ Point.to_shape my_point; Rectangle.to_shape my_rectangle ]

let () = Shape.draw_all my_shapes

Sure, you need to lift each instance manually using the to_shape functions, but the solutions
where you pack a first-class module along with a value also require lifting.

3 Likes