Preservation of invariants while resolving destructuring

I want to determine the type so that the whole structure goes through the members of the module, which can preserve invariants, but allow you to destruct template matching.

I'm just learning OCaml, but the following almost works for an int pair with an 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 this way

 # 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 destructive work after fashion

 # let {Range.left=x; Range.right=y} = p;; val x : int = 1 val y : int = 2 

when building crash

 # 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 really would like to do is the syntactic convenience of destructuring tuples. 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 cannot destroy it using the tuple template:

 # 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 them to be as light as possible in memory size. Any ideas?

+6
source share
5 answers

As explained in manual , you need explicit coercion:

 # let (a, b) = (r :> int*int);; val a : int = 1 val b : int = 2 
+9
source

An easy way to do this is to add the to_tuple function and create an abstract tour type.

 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 
+4
source

Your solution type t = R of (int * int) will be lightweight in terms of memory and syntactically a bit lighter than a coercive solution. OCaml optimizes the data type case with one constructor, so you do not pay for it. (I do not have an official reference to this requirement, but Adam Khlipala (OCaml expert) mentions it here: http://adam.chlipala.net/cpdt/html/Subset.html ).

+3
source

I just checked this test with Objsize (reports OCaml sizes).

 # type fancy = R of int * int;; type fancy = R of int * int # Objsize.objsize (R (3, 5));; - : Objsize.info = {Objsize.data = 2; Objsize.headers = 1; Objsize.depth = 0} # Objsize.objsize (3,5);; - : Objsize.info = {Objsize.data = 2; Objsize.headers = 1; Objsize.depth = 0} 

If you believe these values ​​(what I'm doing), there is no size penalty for using the native type of one constructor instead of a tuple.

+2
source

In fact, OCaml Runtime encoding is very simple. Here's a tool you may find useful https://github.com/bobzhang/caml-inspect

+1
source

Source: https://habr.com/ru/post/916399/


All Articles