module Perms:sig
..end
Here's a hypothetical interface to an on-off switch which uses them:
open Perms.Export
module Switch : sig
type -'permissions t
val create : unit -> [< _ perms] t
val read : [> read] t -> [`On | `Off]
val write : [> write] t -> [`On | `Off] -> unit
end
Note that the permissions parameter must be contravariant -- you are allowed to forget that you have any particular permissions, but not give yourself new permissions.
You can now create different "views" of a switch. For example, in:
let read_write_s1 : read_write Switch.t = Switch.create ()
let read_only_s1 = (read_write_s1 :> read t)
read_write_s1
and read_only_s1
are physically equal, but calling
Switch.write read_only_s1
is a type error, while Switch.write read_write_s1
is
allowed.
Also note that this is a type error:
let s1 = Switch.create ()
let read_write_s1 = (s1 :> read_write t)
let immutable_s1 = (s1 :> immutable t)
which is good, since it would be incorrect if it were allowed. This is enforced by:
1. Having the permissions parameter be contravariant, which causes the compiler to
require that the result of a create ()
call has a concrete type (due to the value
restriction).
2. Ensuring that there is no type that has both read_write
and immutable
as
subtypes. This is why the variants are `Who_can_write of Me.t
and `Who_can_write
of Nobody.t
rather than `I_can_write
and `Nobody_can_write
.
The standard usage pattern is as above:
t
with [< _ perms]
permissions.t
and access it in some way represent that access in the
type.
The reason for having creation functions return a t
with [< _ perms]
permissions
is to help give early warning if you create a t
with a nonsensical permission type
that you wouldn't be able to use with the other functions in the module.
Ideally, this would be done with a constraint on the type in a usage pattern like this:
[< _ perms]
.t
with no constraint on the permissions.t
and access it in some way represent that access in the
type.
Unfortunately, that doesn't work for us due to some quirks in the way constraints of
this form are handled: In particular, they don't work well with with sexp
and they
don't work well with included signatures. But you could try this usage pattern if you
don't do either of those things.
For some types you may expect to always have read permissions, and it may therefore by
annoying to keep rewriting [> read]
. In that case you may want to try this usage
pattern:
[> read]
.t
with [< _ perms]
permissions.t
and access it in some way represent that access in the
type, except that you don't have to specify read permissions.
However, the standard usage pattern is again preferred to this one: constraint
has
lots of sharp edges, and putting [> read]
instead of _
in the types provides
explicitness.
type
nobody
Write.t
and Immutable.t
types.type
me
module Read:sig
..end
module Write:sig
..end
module Immutable:sig
..end
module Read_write:sig
..end
module Upper_bound:sig
..end
module Export:sig
..end
module Stable:sig
..end
val nobody_of_sexp : Sexplib.Sexp.t -> nobody
val sexp_of_nobody : nobody -> Sexplib.Sexp.t
val compare_nobody : nobody -> nobody -> int
val bin_nobody : nobody Bin_prot.Type_class.t
val bin_read_nobody : nobody Bin_prot.Read.reader
val __bin_read_nobody__ : (int -> nobody) Bin_prot.Read.reader
val bin_reader_nobody : nobody Bin_prot.Type_class.reader
val bin_size_nobody : nobody Bin_prot.Size.sizer
val bin_write_nobody : nobody Bin_prot.Write.writer
val bin_writer_nobody : nobody Bin_prot.Type_class.writer
val me_of_sexp : Sexplib.Sexp.t -> me
val sexp_of_me : me -> Sexplib.Sexp.t
val compare_me : me -> me -> int
val bin_me : me Bin_prot.Type_class.t
val bin_read_me : me Bin_prot.Read.reader
val __bin_read_me__ : (int -> me) Bin_prot.Read.reader
val bin_reader_me : me Bin_prot.Type_class.reader
val bin_size_me : me Bin_prot.Size.sizer
val bin_write_me : me Bin_prot.Write.writer
val bin_writer_me : me Bin_prot.Type_class.writer
bin_io
for write
due to a naming conflict with the functions
exported by bin_io
for read_write
. If you want bin_io
for write
, use
Write.t
.