I want to define a type so that all construction goes through module members that can preserve invariants, but allow destructuring for pattern matching.
I'm just learning OCaml but the following almost works for an int pair with the invariant that the left should be strictly less than the right
module Range : sig
type t = private { left:int; right:int }
exception InvalidRange of (int*int)
val make : int -> int -> t
end = struct
type t = { left:int; right:int }
exception InvalidRange of (int*int)
let make left right = if left < right
then { left; right }
else raise (InvalidRange (left, right))
end
which works thus
# let p = Range.make 1 2;;
val p : Range.t = {Range.left = 1; Range.right = 2}
# let q = Range.make 2 1;;
Exception: Range.InvalidRange (2, 1).
and destructuring works after a fashion
# let {Range.left=x; Range.right=y} = p;;
val x : int = 1
val y : int = 2
while constructing fails
# let badp = {Range.left = 2; Range.right = 1};;
let badp = {Range.left = 2; Range.right = 1};;
Error: Cannot create values of the private type Range.t
# open Range;;
# let badp = {left = 2; right=1};;
let badp = {left = 2; right=1};;
Error: Cannot create values of the private type Range.t
but what I would really like to do is have the syntactic convenience of destructuring tuples. The below does not work:
module Range : sig
type t = private int*int
exception InvalidRange of (int*int)
val make : int -> int -> t
end = struct
type t = int*int
exception InvalidRange of (int*int)
let make left right = if left < right
then (left, right)
else raise (InvalidRange (left, right))
end
but then I can't destructure it using a tuple pattern:
# let r = Range.make 1 2 ;;
val r : Range.t = (1, 2)
# let (a, b) = r;;
let (a, b) = r;;
Error: This expression has type Range.t
but an expression was expected of type 'a * 'b
I could change the type to type t = R of (int * int)
but I need these to be as light-weight memory-wise as possible. Any ideas?
As explained in the manual, you need an explicit coercion:
# let (a, b) = (r :> int*int);;
val a : int = 1
val b : int = 2
A simple way to do that is to add a to_tuple
function and make tour type abstract.
module Range : sig
type t
exception InvalidRange of (int*int)
val make : int -> int -> t
val to_tuple : t -> (int * int)
end = struct
type t = { left:int; right:int }
exception InvalidRange of (int*int)
let make left right = if left < right
then { left; right }
else raise (InvalidRange (left, right))
let to_tuple t = t.left, t.right
end
then you can do
let (a, b) = to_tuple range
Your solution of type t = R of (int * int)
will be lightweight in terms of memory, and is syntactically a bit lighter-weight than the coercion solution. OCaml optimizes the case of a single-constructor datatype so you don't pay for it. (I don't have an official reference for this claim, but Adam Chlipala (an OCaml expert) mentions it here: http://adam.chlipala.net/cpdt/html/Subset.html).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With