Edwin,

Thank you for your response.

You are correct that the "val t" in my example should read "type t".

My main goal is one of readability: I want to have large swathes of the codebase only use the immutable interface. A few, but very few, parts of the codebase will use the mutable interface. Ideally, I want the developer reading the .mli files to quickly and easily understand what interfaces do what. The standard way of adding the type constraints add a lot of syntax and make it (slightly) harder to read. My entire question is sort of a nitpick.

I am interested in your use of polymorphic variants to deal with this issue: that would actually give better compiler errors than what I am currently implemented. Thank you for the suggestion. I will mull that and see if I think of any downsides to that approach.

Trevor


On Wed, Aug 6, 2014 at 11:36 AM, Török Edwin <edwin+ml-ocaml@etorok.net> wrote:
On 08/06/2014 03:14 PM, Trevor Smith wrote:
> Hello,
>
> I have a question about using .mli files for increased readability. I think my question boils down to: "Can one tersely add type constraints to a signature defined in a .mli in that same .mli file?"
>
> Detailed problem: You want to have a read interface and a write interface for the same implementation.
>
> We'll use a trivial example with a character and a name.
>
> module type CharacterSig = sig
>   val t
>   val create : string -> t
>   val name : t -> string
> end
>
> module type MutableCharacterSig = sig
>   val t
>   val create : string -> t
>   val name : t -> string
>   val set_name : t -> string -> unit
> end
>
> module CharacterImpl = struct
>   type t = {name : string ref}
>   let create name  =
>     {name = ref name }
>   let name c = !(c.name <http://c.name/>)
>   let set_name c name =
>     c.name <http://c.name/> := name
> end
>
> module Character = (CharacterImpl : CharacterSig with type t = CharacterImpl.t)
> module MutableCharacter = (CharacterImpl : MutableCharacterSig with type t = CharacterImpl.t)
>
> But what I would like is to specify the read and write signatures in .mli files for a more readable codebase.
>
> So:
>
> character.mli:
>   val t

shouldn't this be a type?

>   val create : string -> t
>   val name : t -> string
>
> mCharacter.mli:
>   val t
>   val create : string -> t
>   val name : t -> string
>   val set_name : t -> string -> unit
>
> characterImpl.ml (* ... implementation as above ... *)
>
> However, it is not clear to me that there is a way to attach the type constraint to character.mli and mCharacter.mli, while keeping the terse readability of the .mli file. One idea for a solution, would be to reference a "this" so that the interface could show that it was being implemented by CharacterImpl, and include the type constraint.

Not sure I understood exactly what you want to do, but using 'include module type of' and 'type t = CharacterImpl.t' should work:

character.mli
type t = CharacterImpl.t
val create : string -> t
val name : t -> string

character.ml:
include CharacterImpl

mCharacter.mli:
include module type of Character
val set_name : t -> string -> unit

mCharacter.ml:
include CharacterImpl

characterImpl.ml:
type t = {name : string ref}
let create name  =
  {name = ref name }
let name c = !(c.name)
let set_name c name =
  c.name := name

However in this case Character.t = MCharacter.t = CharacterImpl.t, so you won't get the type safety you want
(A Character.t can still be modified by MCharacter.set_name).

Perhaps it'd be better to use different types, though to_character is not the identity function:

character.mli:
type t
val create : string -> t
val name : t -> string

mCharacter.mli:
include module type of Character
val set_name : t -> string -> unit
val to_character : t -> Character.t

character.ml:
include MCharacter

mCharacter.ml:
type t = {name : string ref}
let create name  =
  {name = ref name }
let name c = !(c.name)
let set_name c name =
  c.name := name

let to_character x = Character.create (name x)

In fact you should probably take a look at String and Bytes type in OCaml 4.02 (and the ocaml-bytes compatibility lib for <4.0.2).

I'd prefer something simpler though:

character.ml:
type u = {name : string ref}
type 'a t = u

let create name  =
  {name = ref name }
let create_ro = create
let create_rw = create
let name c = !(c.name)
let set_name c name =
  c.name := name
let readonly x = x

character.mli:
type 'a t constraint 'a = [< `W | `R]
val create_ro : string -> [`R] t
val create_rw : string -> [`R | `W] t
val name : 'a t -> string
val set_name : [> `W] t -> string -> unit
val readonly : [> `R] t -> [`R] t

Best regards,
--Edwin

--
Caml-list mailing list.  Subscription management and archives:
https://sympa.inria.fr/sympa/arc/caml-list
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
Bug reports: http://caml.inria.fr/bin/caml-bugs