caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: octachron <octa@polychoron.fr>
To: caml-list@inria.fr
Subject: Re: [Caml-list] Covariant GADTs
Date: Mon, 19 Sep 2016 10:58:29 +0200	[thread overview]
Message-ID: <a4859b23-5300-4205-3cac-3f19888c30dd@polychoron.fr> (raw)
In-Reply-To: <CAP_800q7jRazR95e4PiFA-9K_3+G2aok_Cvt_8+9bMfVZs9RbQ@mail.gmail.com>

Hi Markus,

> Taking an element link can be done without
> having to introduce superfluous pattern matches, because the GADT
> guarantees via the type system that a link is non-empty.

I fear that you are misinterpreting the guarantees giving to you by the 
type ('el, 'kind) Link.t. This type can be rewritten as

   type ('el, _) t =
     | Empty : ('el, [ `empty ]) t
     | Elt : {
         mutable prev : ('el, [< kind ] as 'prev ) t;
         el : 'el;
         mutable next : ('el, [< kind ] as 'next ) t;
       } -> ('el, [ `elt ]) t

Notice the existential types 'prev and 'next within the Elt constructor: 
they mean that outside of the context of Elt, the type system simply 
does not know the subtype of the prev and next fields. Even within the 
context of the `Elt` constructor, the type system only knows that there 
exists two types p and n such that Elt r is typed Elt 
{prev:p;next:n;el:el}. But the information about which precise type was 
present when constructing the value is lost.

Therefore, these fields are neither readable nor writable directly. A 
direct manifestation of the problem is that, as you observed, you cannot 
assign new values to either prev or next without use of `Obj.magic`. For 
instance,

let set_next (Elt r) x = r.next <- x;;

fails with

Error: This expression has type 'a but an expression was expected of 
type ('b, [< kind ]) t. The type constructor $Elt_'next would escape its 
scope.

because the type checker tries to unify the type 'a of x with the 
existential type $Elt_'next of `next`. Using a fantasy syntax, we have

let set_next (type a) (Elt (type p n) {next:n; prev:p; _} ) (x:a) = 
r.next:p <- x:a;;

and the types `a` and `p` cannot be unified because `p` should not 
escape the context of the constructor Elt.

Consequently writing `set_next` requires to use Obj.magic:

let set_next (Elt r) x = r.next <- Obj.magic x;;

This use of Obj.magic should be fine, since it is impossible to access 
the field prev and next directly. Contrarily, your coerce function is 
unsafe:

let current (Elt {el;_}) = el
let crash () = current (coerce @@ Empty)

On the other side of the problem, this is the reason why you need to 
wrap access with an option in `get_opt_next`. Note that I am not certain 
that this wrapping does not completely defeat your optimisation objective.

Regards − octachron.


Le 09/19/16 à 03:52, Markus Mottl a écrit :
> Hi Petter,
>
> thanks, the above approach obviously works with my previous example,
> which I had sadly oversimplified.  In my case I was actually dealing
> with inlined, mutable records where the above won't work, because then
> I can't overwrite fields.  The somewhat more complete example also
> contains strange type system behavior I don't understand that may even
> be a bug.
>
> The example below shows how GADTs + inline records + (if available)
> covariance could help implement doubly linked lists much more
> efficiently.  The memory representation using mutable inline records
> is essentially optimal.  Taking an element link can be done without
> having to introduce superfluous pattern matches, because the GADT
> guarantees via the type system that a link is non-empty.
>
> I had to use "Obj.magic" due to the lack of covariance support with
> GADTs in the "coerce" function, which I believe to be sound.
>
> The strange behavior is in the "insert_first" function and also the
> "create" function: "Empty" is not of covariant type, but nevertheless
> can be used for allocation in that way (e.g. in the "let res = ..."
> part).  But the commented out lines show that I cannot overwrite the
> exact same field in the just allocated value with the exact same
> value.  I can understand the reason why the latter is not possible,
> but why is allocation allowed that way?  Maybe a type system guru can
> explain what's going on.
>
> ----------
> module Link = struct
>    type kind = [ `empty | `elt ]
>
>    type ('el, _) t =
>      | Empty : ('el, [ `empty ]) t
>      | Elt : {
>          mutable prev : ('el, [< kind ]) t;
>          el : 'el;
>          mutable next : ('el, [< kind ]) t;
>        } -> ('el, [ `elt ]) t
>
>    let coerce (t : ('el, [< kind ]) t) : ('el, [< kind ]) t = Obj.magic t
>
>    let get_opt_elt : type a. ('el, a) t -> ('el, [ `elt ]) t option = function
>      | Empty -> None
>      | Elt _ as elt -> Some elt
>
>    let cut : type a. ('el, a) t -> unit = function
>      | Empty -> ()
>      | Elt { prev = prev_elt; next = next_elt } ->
>          match prev_elt, next_elt with
>          | Empty, Empty -> ()
>          | Empty, Elt next -> next.prev <- coerce Empty
>          | Elt prev, Empty -> prev.next <- coerce Empty
>          | Elt prev, Elt next ->
>              prev.next <- coerce next_elt;
>              next.prev <- coerce prev_elt
> end  (* Link *)
>
> module Doubly_linked : sig
>    type 'el t
>    type 'el elt
>
>    val create : unit -> 'el t
>    val first : 'el t -> 'el elt option
>    val insert_first : 'el t -> 'el -> unit
>    val remove : 'el elt -> unit
> end = struct
>    open Link
>
>    type 'el t = Head : { mutable head : ('el, [< Link.kind ]) Link.t } -> 'el t
>    type 'el elt = ('el, [ `elt ]) Link.t
>
>    let create () = Head { head = Empty }
>
>    let first (Head h) = Link.get_opt_elt h.head
>
>    let insert_first (Head h) el =
>      h.head <-
>        match h.head with
>        | Empty ->
>            let res = Elt { prev = Empty; el; next = Empty } in
>            (* let Elt foo = res in *)
>            (* foo.prev <- Empty; *)
>            coerce res
>        | Elt _ as next -> coerce (Elt { prev = Empty; el; next })
>
>    let remove = Link.cut
> end  (* Doubly_linked *)
> ----------
>
> Regards,
> Markus
>
>


  reply	other threads:[~2016-09-19  8:58 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-09-17 17:38 Markus Mottl
2016-09-18  8:17 ` Petter A. Urkedal
2016-09-19  1:52   ` Markus Mottl
2016-09-19  8:58     ` octachron [this message]
2016-09-19 10:18       ` Mikhail Mandrykin
2016-09-19 13:37         ` Mikhail Mandrykin
2016-09-19 14:46         ` Markus Mottl
2016-09-19 14:53           ` Mikhail Mandrykin
2016-09-19 15:03             ` Markus Mottl
2016-09-20 21:07               ` Markus Mottl
2016-09-21 10:11                 ` Lukasz Stafiniak
2016-09-21 10:14                   ` Lukasz Stafiniak
2016-09-21 17:04                     ` Markus Mottl
2016-09-21 21:40                       ` Gabriel Scherer
2016-09-22  0:39                         ` Markus Mottl
2016-09-24  5:09                           ` Yaron Minsky
2016-10-04 10:33                 ` Jacques Garrigue
2016-09-19 14:39       ` Markus Mottl
2016-09-19 10:05     ` Goswin von Brederlow

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=a4859b23-5300-4205-3cac-3f19888c30dd@polychoron.fr \
    --to=octa@polychoron.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).