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.