Search code examples
typesmoduletuplesocamlencapsulation

Why can't match private tuples?


Ocaml provides three kinds of encapsulation for types:

  • abstract - when we can do nothing with an object of abstract type outside the module (can't read, create, update)
  • public - when we can do everything with an object of public type outside the module (can read, create, update)
  • private - when we can only read from an object of public type outside the module (can read but can't create or update)

Everything works as described but reading anything from a private tuple is impossible.

For example, the following example with a record type works fine:

module Private : sig
  type t = private { a : int }

  val make : int -> t
end = struct
  type t = { a : int }

  let make a = { a } 
end

let x = Private.make 5
let a = x.a
let Private.{ a = value } = x

But it is impossible to extract any value in the relevant example with a tuple.

module Private : sig
  type t = private int * int

  val make : int -> int -> t
end = struct
  type t = int * int

  let make a b = a, b
end

let x = Private.make 5 6
let Private.(a, b) = x

Compiler gives me the error for the last line of code:

Error: This expression has type Private.t but an expression was expected of type 'a * 'b

I tried many different failed attempts of reading from private tuples. And my result is that there is no way to do that.

Is it possible to do so? If it is not, does it destroy the conception of private types or do I miss something in my understanding?


Note

I have checked that it is impossible to execute private functions too.

module Private : sig
  type t = private int -> int

  val make : int -> int -> t
end = struct
  type t = int -> int

  let make a b = fun x -> if x = b then b else 0
end

let f = Private.make 5 6
let res = f 5

Error: This expression has type Private.t This is not a function; it cannot be applied.


Solution

  • tl;dr: this will work:

    let (a, b) = (x :> int * int)
    

    There's a difference between private record and variant types (which are nominal types), and private type abbreviations. And private row types, but let's not go there.

    Private record and variant types can be destructured, private type abbreviations cannot. They can be coerced to their underlying type however, which is what the above does.

    You can perhaps get an intuition for why this is if you consider the case of a single private int. The purpose there is often to make sure you can't freely mix it with plain ints. For example:

    module Id : sig
      type t = private int
    
      val make : int -> t
    end = struct
      type t = int
    
      let make a = a
    end
    
    let my_id = Id.make 42
    let other_id = my_id + 3 (* This is probably not what you want *)
    let x: int = (my_id :> int) + 3 (* This works, and is unambiguous *)
    

    And since a pair of ints is just that, it makes sense (to me at least) that it would behave the same.

    See also the section on private types in the OCaml manual.