Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserving invariants while allowing destructuring

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?

like image 221
Mike Samuel Avatar asked May 22 '12 20:05

Mike Samuel


3 Answers

As explained in the manual, you need an explicit coercion:

# let (a, b) = (r :> int*int);;
val a : int = 1
val b : int = 2
like image 124
esope Avatar answered Nov 10 '22 04:11

esope


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
like image 4
Thomash Avatar answered Nov 10 '22 03:11

Thomash


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).

like image 3
Yitzhak Mandelbaum Avatar answered Nov 10 '22 04:11

Yitzhak Mandelbaum