caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Nicolas Ojeda Bar <nicolas.ojeda.bar@lexifi.com>
To: "Jocelyn Sérot" <Jocelyn.Serot@univ-bpclermont.fr>
Cc: OCaml Mailing List <caml-list@inria.fr>
Subject: Re: [Caml-list] Q: functors and "has a" inheritance
Date: Wed, 6 Jul 2016 09:49:31 +0200	[thread overview]
Message-ID: <CADK7aFMxVDE+DDtOw-vpYypMuN-7rBS1YGwQc1wUsO2p6HiLCg@mail.gmail.com> (raw)
In-Reply-To: <64440D5D-1FC8-4C53-9520-D9D3D21F8FE5@univ-bpclermont.fr>

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

Hi Jocelyn

One issue is that you have two modules, P and R.S, of the form Set.Make(X),
Set.Make (X') for modules X and X' which are structurally equal.
Unfortunately this is not enough for the OCaml module system to deduce that
P.t and R.S.t are compatible.  In general if F is a functor with output
signature S and t is abstract type in S, then F(X).t and F(X').t will be
compatible exactly when X and X' are literally the same module.  I don't
think you will be able to fix this by adding type sharing constrains.

Cheers
Nicolas


On Tue, Jul 5, 2016 at 5:25 PM, Jocelyn Sérot <
Jocelyn.Serot@univ-bpclermont.fr> wrote:

> Dear all,
>
> I’m stuck with a problem related with the use of functors for implementing
> a library.
> The library concerns Labeled Transition Systems but i’ll present it in a
> simplified version using sets.
>
> Suppose i have a (very simplified !) Set module, which i will call Myset
> to distinguish from that of the standard library :
>
> ———— myset.mli
> module type T = sig
>   type elt
>   type t
>   val empty: t
>   val add: elt -> t -> t
>   val elems: t -> elt list
>   val fold: (elt -> 'a -> 'a) -> t -> 'a -> 'a
> end
>
> module Make (E : Set.OrderedType) : T with type elt = E.t
> ———
>
> ———— myset.ml
> module type T = sig … (* idem myset.mli *) end
>
> module Make (E : Set.OrderedType) = struct
>   module Elt = E
>   type elt = E.t
>   type t = { elems: elt list; }
>   let empty = { elems = [] }
>   let add q s = { elems = q :: s.elems }  (* obviously wrong, but does not
> matter here ! *)
>   let elems s = s.elems
>   let fold f s z = List.fold_left (fun z e -> f e z) z s.elems
> end
> ———
>
> First, i add a functor for computing the product of two sets :
>
> ———— myset.mli (cont’d)
> module Product (S1: T) (S2: T) :
> sig
>   include T with type elt = S1.elt * S2.elt
>   val product: S1.t -> S2.t -> t
> end
> ———
>
> ———— myset.ml (cont’d)
> module Product
>   (S1: T)
>   (S2: T) =
> struct
>   module R =
>     Make (struct type t = S1.elt * S2.elt let compare = compare end)
>     include R
>     let product s1 s2 =
>       S1.fold
>         (fun q1 z ->
>            S2.fold
>              (fun q2 z -> R.add (q1,q2) z)
>              s2
>              z)
>         s1
>         R.empty
> end
> ———
>
> Here’s a typical usage of the Myset module :
>
> —— ex1.ml
> module IntSet = Myset.Make (struct type t = int let compare = compare end)
> module StringSet = Myset.Make (struct type t = string let compare =
> compare end)
>
> let s1 = IntSet.add 1 (IntSet.add 2 IntSet.empty)
> let s2 = StringSet.add "a" (StringSet.add "b" StringSet.empty)
>
> module IntStringSet = Myset.Product (IntSet) (StringSet)
>
> let s3 = IntStringSet.product s1 s2
> ——
>
> So far, so good.
>
> Now suppose i want to « augment » the Myset module so that some kind of
> attribute is attached to each set element. I could of course just modify
> the definition of type [t] and the related functions in the files [
> myset.ml] and [myset.mli]. But suppose i want to reuse as much as
> possible the code already written. My idea is define a new module - let’s
> call it [myseta] (« a » for attributes) - in which the type [t] will
> include a type [Myset.t] and the definitions of this module will make use,
> as much as possible, of those defined in [Myset].
>
> Here’s a first proposal (excluding the Product functor for the moment) :
>
> ———— myseta.mli
> module type Attr = sig type t end
>
> module type T = sig
>   type elt
>   type attr
>   type t
>   module S: Myset.T
>   val empty: t
>   val add: elt * attr -> t -> t
>   val elems: t -> elt list
>   val attrs: t -> (elt * attr) list
>   val set_of: t -> S.t
>   val fold: (elt * attr -> 'a -> 'a) -> t -> 'a -> 'a
> end
>
> module Make (E : Set.OrderedType) (A: Attr) : T with type elt = E.t and
> type attr = A.t
> ———
>
> ———— myseta.ml
> module type Attr = sig type t end
>
> module type T = sig (* idem myseta.mli *) end
>
> module Make (E : Set.OrderedType) (A : Attr) = struct
>   module Elt = E
>   type elt = E.t
>   type attr = A.t
>   module S = Myset.Make(E)
>   type t = { elems: S.t; attrs: (elt * attr) list }
>   let empty = { elems = S.empty; attrs = [] }
>   let add (e,a) s = { elems = S.add e s.elems; attrs = (e,a) :: s.attrs }
>   let elems s = S.elems s.elems
>   let attrs s = s.attrs
>   let set_of s = s.elems
>   let fold f s z = List.fold_left (fun z e -> f e z) z s.attrs
> end
> ———
>
> In practice, of course the [Attr] signature will include other
> specifications.
> In a sense, this is a « has a » inheritance : whenever i build a [Myseta]
> module, i actually build a [Myset] sub-module and this module is used to
> implement all the set-related operations.
> Again, so far, so good.
> The problem shows when i try to define the [Product] functor for the
> [Myseta] module :
> It’s signature is similar to that of the [Myset.Product] functor, with an
> added sharing constraint for attributes (in fact, we could imagine a more
> sophisticated scheme for merging attributes but cartesian product is here) :
>
> ———— myset.mli (cont’d)
> module Product (S1: T) (S2: T) :
> sig
>   include T with type elt = S1.elt * S2.elt
>              and type attr = S1.attr * S2.attr
>   val product: S1.t -> S2.t -> t
> end
> ———
>
> Now, here’s my current implementation
>
> ———— myset.ml (cont’d)
> module Product
>   (S1: T)
>   (S2: T) =
> struct
>   module R =
>     Make
>       (struct type t = S1.elt * S2.elt let compare = compare end)
>       (struct type t = S1.attr * S2.attr let compare = compare end)
>   include R
>   module P = Myset.Product(S1.S)(S2.S)
>   let product s1 s2 =
>     { elems = P.product (S1.set_of s1) (S2.set_of s2);
>             attrs =
>         List.fold_left
>           (fun acc (e1,a1) ->
>              List.fold_left (fun acc (e2,a2) -> ((e1,e2),(a1,a2))::acc)
> acc (S2.attrs s2))
>           []
>           (S1.attrs s1) }
> end
> ———
>
> I use the [Myseta.Make] functor for building the resulting module [named R
> here]. For defining the [product] function, i first use the [Myset.Product]
> functor applied on the two related sub-modules [S1] and [S2] to build the
> product module (named P here) and re-use the [product] function of this
> module to compute the [elems] component of the result. The other component
> is computed directly.
> The problem is that when i try to compile this i get this message :
>
> *File "myseta.ml <http://myseta.ml>", line 44, characters 14-53:*
> *Error: This expression has type P.t = Myset.Product(S1.S)(S2.S).t*
> *       but an expression was expected of type S.t = R.S.t*
>
> My intuition is that a sharing constraint is missing somewhere but i just
> cannot figure out where to add it.
> I tried to rewrite the signature of the [Myseta.Product] functor (in
> [myseta.mli]) as :
>
> module Product (S1: T) (S2: T) :
> sig
>   include T with type elt = S1.elt * S2.elt
>              and type attr = S1.attr * S2.attr
>              and type S.t = Myset.Product(S1.S)(S2.S).t  (* added
> constraint *)
>   val product: S1.t -> S2.t -> t
> end
>
> but it did not change anything..
>
> So my question is : is my diagnostic correct and, if yes, which
> constraint(s) are missing and where; or, conversely, am i completely
> « misusing » the functor mechanisms for implementing this kind of « reuse
> by inclusion » ?
>
> Any help will be grealy appreciated : i’ve been reading and re-reading
> about functors for the last two days but have the impression that at this
> step, things get more and more opaque.. :-S
>
> In anycase, the source code is here :
> http://filez.univ-bpclermont.fr/lamuemlqpm
>
> Jocelyn
>

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

  reply	other threads:[~2016-07-06  7:49 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-07-05 15:25 Jocelyn Sérot
2016-07-06  7:49 ` Nicolas Ojeda Bar [this message]
2016-07-06  8:44   ` Jocelyn Sérot
2016-07-06  9:54     ` Gerd Stolpmann
2016-07-06 12:59       ` Mikhail Mandrykin
2016-07-06 13:35         ` Jocelyn Sérot
2016-07-06 10:15     ` Petter Urkedal
2016-07-06 12:29       ` Jocelyn Sérot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CADK7aFMxVDE+DDtOw-vpYypMuN-7rBS1YGwQc1wUsO2p6HiLCg@mail.gmail.com \
    --to=nicolas.ojeda.bar@lexifi.com \
    --cc=Jocelyn.Serot@univ-bpclermont.fr \
    --cc=caml-list@inria.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).