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