caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] Type Constraints and .mli
@ 2014-08-06 12:14 Trevor Smith
  2014-08-06 15:36 ` Török Edwin
  2014-08-06 22:06 ` Nick Lucaroni
  0 siblings, 2 replies; 5+ messages in thread
From: Trevor Smith @ 2014-08-06 12:14 UTC (permalink / raw)
  To: caml-list

[-- Attachment #1: Type: text/plain, Size: 2214 bytes --]

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)
  let set_name c name =
    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
  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.

The solution I've come to use, that is still pretty readable, is to define
the signature in the .ml file (so no .mli file) and then defining an
internal module which I include (so that I can still reference file name as
the module). So:

character.ml

module type CharacterSig = sig
    type t
    val create : string -> t
    val name : t -> string
end

module T = (CharacterImpl : CharacterSig with type t = CharacterImpl.t)
include T

However, it seems like there could be a slightly more readable way of doing
this.

Thoughts? Thank you.

Trevor

[-- Attachment #2: Type: text/html, Size: 7599 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] Type Constraints and .mli
  2014-08-06 12:14 [Caml-list] Type Constraints and .mli Trevor Smith
@ 2014-08-06 15:36 ` Török Edwin
  2014-08-07 22:06   ` Trevor Smith
  2014-08-06 22:06 ` Nick Lucaroni
  1 sibling, 1 reply; 5+ messages in thread
From: Török Edwin @ 2014-08-06 15:36 UTC (permalink / raw)
  To: caml-list

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] Type Constraints and .mli
  2014-08-06 12:14 [Caml-list] Type Constraints and .mli Trevor Smith
  2014-08-06 15:36 ` Török Edwin
@ 2014-08-06 22:06 ` Nick Lucaroni
  1 sibling, 0 replies; 5+ messages in thread
From: Nick Lucaroni @ 2014-08-06 22:06 UTC (permalink / raw)
  To: Trevor Smith; +Cc: caml-list

[-- Attachment #1: Type: text/plain, Size: 2477 bytes --]

I had come to the same conclusion.
On Aug 6, 2014 8:14 AM, "Trevor Smith" <trevorsummerssmith@gmail.com> 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)
>   let set_name c name =
>     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
>   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.
>
> The solution I've come to use, that is still pretty readable, is to define
> the signature in the .ml file (so no .mli file) and then defining an
> internal module which I include (so that I can still reference file name as
> the module). So:
>
> character.ml
>
> module type CharacterSig = sig
>     type t
>     val create : string -> t
>     val name : t -> string
> end
>
> module T = (CharacterImpl : CharacterSig with type t = CharacterImpl.t)
> include T
>
> However, it seems like there could be a slightly more readable way of
> doing this.
>
> Thoughts? Thank you.
>
> Trevor
>

[-- Attachment #2: Type: text/html, Size: 8039 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] Type Constraints and .mli
  2014-08-06 15:36 ` Török Edwin
@ 2014-08-07 22:06   ` Trevor Smith
  2014-08-08  8:19     ` Frédéric Bour
  0 siblings, 1 reply; 5+ messages in thread
From: Trevor Smith @ 2014-08-07 22:06 UTC (permalink / raw)
  To: Török Edwin; +Cc: caml-list

[-- Attachment #1: Type: text/plain, Size: 5334 bytes --]

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
>

[-- Attachment #2: Type: text/html, Size: 7493 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] Type Constraints and .mli
  2014-08-07 22:06   ` Trevor Smith
@ 2014-08-08  8:19     ` Frédéric Bour
  0 siblings, 0 replies; 5+ messages in thread
From: Frédéric Bour @ 2014-08-08  8:19 UTC (permalink / raw)
  To: Trevor Smith; +Cc: Török Edwin, caml-list

[-- Attachment #1: Type: text/plain, Size: 1324 bytes --]

Le jeu. 7 août 2014 à 23:06, Trevor Smith 
<trevorsummerssmith@gmail.com> a écrit :
> 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.

Hi, I am not sure I understood your problem but I'll try to provide 
some info.

The standard way of adding "type constraints" is by just exposing the 
equality between types in the interface. In your case:

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

mCharacter.mli:
  type t = CharacterImpl.t
  val create : string -> t
  val name : t -> string
  val set_name : t -> string -> unit

Which is what express "CharacterSig with type t = CharacterImpl.t":
create a new signature by taking "CharacterSig" and replacing "type t" 
by "type t = CharacterImpl.t".



[-- Attachment #2: Type: text/html, Size: 2636 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2014-08-08  8:20 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-08-06 12:14 [Caml-list] Type Constraints and .mli Trevor Smith
2014-08-06 15:36 ` Török Edwin
2014-08-07 22:06   ` Trevor Smith
2014-08-08  8:19     ` Frédéric Bour
2014-08-06 22:06 ` Nick Lucaroni

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).