Question

I'm using phantom types to emulate the state of a stack, as a wrapper module for ocaml-lua (Lua communicates with C/OCaml through a stack). Small code example:

type 's t
type empty
type 's table
type top

val newstate : unit -> empty t           (* stack empty *)
val getglobal : empty t -> top table t   (* stack: -1 => table *)

Certain stack operations are possible on both tables and array-tables (no real arrays in Lua); some aren't. So if I have types

type 's table
type 's array

I want a kind like table-or-array for functions that operate on both types, but still being able to forbid e.g. rawgeti (array operation) on tables. objlen is a stack operation that returns the "length" of the element at the top of the stack. This element can be both table or array-table. At the moment, the wrapper function is defined as follows:

val objlen : (top table) t -> int

What I want is

val objlen : (top table-or-array) t -> int

That is, array and table is subtypes of table-or-array.

Any ideas?

Regards Olle

Edit

After considerations, I came up with this:

module M : sig
  type ('s, 't) t

  (* New Lua state with empty stack *)
  val newstate : unit -> (unit, unit) t

  (* Get table *)
  val getglobal : ('a) t -> ([< `table | `string | `number | `fn], 'a) t

  (* Get array index and put "anything" on top of stack *)
  val rawgeti : ([`table], 'a) t -> ([< `table | `string | `number | `fn], [`table] * 'a) t

  (* String on top of stack *)
  val tostring : ([`string], _) t -> string

  (* Table or array-table on top of stack *)
  val objlen : ([`table], _) t -> int

  val pop : ('a, 'b * 'c) t -> ('b, 'c) t
end = struct
  type top
  type ('s, 't) t = string      (* Should really be Lua_api.Lua.state *)

  (* Dummy implementations *)
  let newstate () = "state"
  let gettable s = s
  let getarray s = s
  let rawgeti s = s
  let tostring s = "Hello phantom world!"
  let objlen s = 10
  let pop s = s
end

The stack at type level should now know neither more nor less than the stack itself. E.g. rawgeti will push on stack any type.

Was it helpful?

Solution

What about the following structure?

type ('data, 'kind) t
type array_kind
type stack_kind

(* use tuples as type-level lists:
   (a * (b * (c * unit))) for the stack of type-shape [a;b;c] *)
val newstate : unit -> (unit, stack) t
val getglobal : (unit, stack) t -> (top * unit, stack) t

val objlen : (top * 'a, 'k) t -> int

This uses polymorphism (on 'k) to express that "any kind is fine". Using polymorphic variants, it is possible to use subtyping instead, but I would rather advise against it, because it is more complex and its interaction with GADTs (which you want to use internally to implement your phantom-types signature, or maybe directly expose GADTs) is more problematic.

PS: this is exactly how the standard Bigarray module uses "kind types" for a similar purpose.

Edit: the formulation above is a bit awkward as polymorphic variant also use polymorphism (subtyping being used in restricted specific cases), and it's possible to only use polymorphism in type-level variants. My remark about excessive complexity of this solution still stands.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top