* [Caml-list] Encoding "links" with the type system
@ 2016-09-21 19:12 Andre Nathan
2016-09-21 22:22 ` Jeremy Yallop
0 siblings, 1 reply; 9+ messages in thread
From: Andre Nathan @ 2016-09-21 19:12 UTC (permalink / raw)
To: caml-list
[-- Attachment #1.1: Type: text/plain, Size: 2209 bytes --]
Hi
I'm trying to encode links (an example would be directed graph edges)
using the type system.
The idea is to have types such as
module Source = struct
type 'a t = { name : string }
let create name = { name }
end
module Sink = struct
type 'a t = { name : string }
let create name = { name }
end
module Link = struct
type ('a, 'b) t = 'a Source.t * 'b Sink.t
end
and then define a "link set" with the following characteristics:
* You initialize a link set with a sink;
* You can add links to set such that
* The first link's sink must be of the type of the sink;
* Additional links can have as sink types the original sink type
from the set creation, or the source types of previously added
links.
In other words, if a set is created from a "t1 Sink.t", the first link
on the set must be of type "t2 Source.t * t1 Sink.t", the second link
can be either "t3 Source.t * t1 Sink.t" or "t3 Source.t * t2 Sink.t" and
so on.
Is it possible at all to encode such invariants using the type system? I
couldn't get past something like
module Set = struct
type _ t =
| S : 'a Sink.t -> 'a t
| L : 'a t * ('b, 'a) Link.t -> ('a * 'b) t
let create sink = S sink
let add_link link set = L (set, link)
end
This allows me to insert the first link:
type t1
type t2
type t3
let _ =
let snk1 : t1 Sink.t = Sink.create "sink1" in
let set = Set.create snk1 in
let src1 : t2 Source.t = Source.create "source1" in
let lnk1 = (src1, snk1) in
let set1 = Set.add_link lnk1 set in
...
but now "set1" has type "(t1 * t2) Set.t" and I can't call "add_link" on
it anymore:
...
let src2 : t3 Source.t = Source.create "source2" in
let lnk2 = (src2, snk1) in
let set2 = Set.add_link lnk2 set1 in
...
which gives me "This expression has type (t1 * t2) Set.t but an
expression was expected of type t1 Set.t" when passing "set1" to "add_link".
I also tried to come up with a solution using difference lists for the
set but hit basically the same problem as above.
Any help would be appreciated.
Thanks,
Andre
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 473 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-09-21 19:12 [Caml-list] Encoding "links" with the type system Andre Nathan
@ 2016-09-21 22:22 ` Jeremy Yallop
2016-09-22 0:50 ` Andre Nathan
2016-10-07 17:02 ` Shayne Fletcher
0 siblings, 2 replies; 9+ messages in thread
From: Jeremy Yallop @ 2016-09-21 22:22 UTC (permalink / raw)
To: Andre Nathan; +Cc: caml-list
Dear Andre,
On 21 September 2016 at 20:12, Andre Nathan <andre@digirati.com.br> wrote:
> I'm trying to encode links (an example would be directed graph edges)
> using the type system.
>
> The idea is to have types such as
>
> module Source = struct
> type 'a t = { name : string }
> let create name = { name }
> end
>
> module Sink = struct
> type 'a t = { name : string }
> let create name = { name }
> end
>
> module Link = struct
> type ('a, 'b) t = 'a Source.t * 'b Sink.t
> end
>
>
> and then define a "link set" with the following characteristics:
>
> * You initialize a link set with a sink;
> * You can add links to set such that
> * The first link's sink must be of the type of the sink;
> * Additional links can have as sink types the original sink type
> from the set creation, or the source types of previously added
> links.
>
> In other words, if a set is created from a "t1 Sink.t", the first link
> on the set must be of type "t2 Source.t * t1 Sink.t", the second link
> can be either "t3 Source.t * t1 Sink.t" or "t3 Source.t * t2 Sink.t" and
> so on.
>
> Is it possible at all to encode such invariants using the type system?
It is! Here's one approach, which is based on two ideas:
1. generating a fresh, unique type for each set
(the paper "Lightweight Static Capabilities" calls these "type
eigenvariables")
2. an encoding of a set as a collection of evidence about membership.
I'll describe an interface based on these ideas that maintains the
properties you stipulate. I'll leave the problem of building an
implementation that satisfies the interface to you. (In fact, the
interface is the tricky part and the implementation is straightforward
if you can treat all the type parameters as phantom.)
First, we'll need types for the objects in the domain: sources, sinks,
sets and links.
type _ source and _ sink and _ set and ('source, 'sink) link
Besides these we'll also need a type of membership evidence. A value
of type '(sink, set) accepts' is evidence that 'set' will accept a
link with sink type 'sink':
type ('sink, 'set) accepts
Then there are two operations on sets: creating a fresh set and adding
a link to a set. In both cases the operation returns multiple values,
so I'll use records for the return types. Furthermore, both
operations create fresh types (since sets have unique types), so I'll
use records with existential types.
First, here's the type of the result of the create_set operation:
type 'sink fresh_set = Fresh_set :{ (* :{ *)
set: 't set;
accepts: ('sink, 't) accepts;
} -> 'sink fresh_set
There's a type parameter 'sink, which'll be instantiated with the type
of the sink used to create the set, and two fields:
1. 'set' is the new set
2. 'accepts' is evidence that 'set' will accept a link with sink type 'sink'
(or, if you like, a capability that will allow us to add such a link)
Note that 'sink is a type parameter, since we need to ensure that it
is the same as another type in the program (the sink used to create
the set), but 't is existential, since we need to ensure that it is
distinct from every other type in the program (since it uniquely
identifies 'set').
The create_set operation builds a fresh set from a sink:
val create_set : 'sink sink -> 'sink fresh_set
Next, here's the type of the result of the add_link operation:
type ('sink, 'parent) augmented_set = Augmented_set :{
set: 't set;
accepts: ('sink, 't) accepts;
cc: 's. ('s, 'parent) accepts -> ('s, 't) accepts;
} -> ('sink, 'parent) augmented_set
This time there are three elements to the result:
1. 'set' is the new set (as before)
2. 'accepts' is evidence that 'set' will accept a link with sink
type 'sink' (as before)
3. 'cc' is a capability converter, that turns evidence that the
"parent" set will accept a link into evidence that 'set' will accept
the same link.
(Another of looking at this is that 'augmented_set' bundles the new
set up along with evidence that 'sink' is a member and evidence that
every member of the parent set is also a member.)
(And it's again worth looking at the scope of the type variables:
'sink and 'parent are parameters, since they correspond to the types
of inputs to the operation; 't is fresh, and 's is
universally-quantified, since the capability converter must work for
any piece of evidence involving the parent.)
The insert_link operation takes three arguments: the link to insert,
the set into which the link is inserted, and evidence that the
insertion is acceptable:
val insert_link :
('source, 'sink) link -> (* the link *)
't set -> (* the set *)
('sink, 't) accepts -> (* evidence that the set accepts the link *)
('source, 't) augmented_set
Here's the interface in action. Let's assume that we have your sinks,
sources and links:
type t1 and t2 and t3
val snk1 : t1 sink
val src1 : t2 source
val src2 : t3 source
val lnk1 : (t2, t1) link
val lnk2 : (t3, t1) link
val lnk3 : (t3, t2) link
Let's start by building a set from snk1:
let Fresh_set { set = set; accepts = a } =
create_set snk1 in
Now, since 'set' accepts links with sink type 't1' (as attested by
'a') we can insert a new link:
let Augmented_set { set = set1; accepts = a1; cc = cc1 } =
insert_link lnk1 set a in
We now have the following evidence available:
'a' says that 'set' accepts 't1'
'a1' says that 'set1' accepts 't2'
'cc1' says that 'set1' accepts anything that 'set' accepts, and so
'cc1 a' says that set1' accepts 't1'
and so we can insert links with sink type either 't1' or 't2' into 'set1':
insert_link lnk3 set1 a1
insert_link lnk2 set1 (cc1 a)
And, of course, since we can only insert links when we have evidence
that the set will accept them, there's no way to perform invalid
insertions.
One drawback of the above is a possible lack of efficiency, mostly
depending on how 'cc' is implemented. In fact, there's also a
cost-free approach to capability conversions based on evidence
subtyping and private types, but I'll leave it as an exercise for the
reader.
I hope that helps!
Kind regards,
Jeremy.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-09-21 22:22 ` Jeremy Yallop
@ 2016-09-22 0:50 ` Andre Nathan
2016-09-30 13:54 ` Gabriel Scherer
2016-10-07 17:02 ` Shayne Fletcher
1 sibling, 1 reply; 9+ messages in thread
From: Andre Nathan @ 2016-09-22 0:50 UTC (permalink / raw)
To: Jeremy Yallop; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 336 bytes --]
> Em 21 de set de 2016, às 19:22, Jeremy Yallop <yallop@gmail.com> escreveu:
> It is! Here's one approach, which is based on two ideas:
Wow, there's a lot to digest here :) Most of it is new to me, including the use of "evidence" types... I'll have to re-read it and try to work it out after a night of sleep.
Thanks,
Andre
[-- Attachment #2: Type: text/html, Size: 1432 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-09-22 0:50 ` Andre Nathan
@ 2016-09-30 13:54 ` Gabriel Scherer
2016-10-05 19:46 ` Andre Nathan
0 siblings, 1 reply; 9+ messages in thread
From: Gabriel Scherer @ 2016-09-30 13:54 UTC (permalink / raw)
To: Andre Nathan; +Cc: Jeremy Yallop, caml users
[-- Attachment #1: Type: text/plain, Size: 2380 bytes --]
I came back to this thread thanks to Alan Schmitt's Caml Weekly News.
Here is a fairly systematic approach to the problem, contrasting Jeremy's
more virtuoso approach. It works well when the whole set is statically
defined in the source code, but I'm not sure about more dynamic scenarios
(when a lot of the type variables involved become existentially quantified.
The idea is to first formulate a solution using logic programming. A link
set can be represented as a list of accepted sinks, and I can write a small
Prolog program, "insert" that takes a new link, and a link set, and
traverses the link set to find the link's sink in the list, and add the
link's source to the list in passing.
set([sink]) :- link(source, sink).
set(sinks) :-
set(prev_sinks),
link(source, sink),
insert(prev_sinks, source, sink, sinks).
(* either the sink we want to insert is first in the list *)
insert([sink | _], source, sink, [source, sink | sinks]).
(* or it is to be found somewhere, recursively, under some other sink
[s] *)
insert([s | prev_sinks], source, sink, [s | sinks]) :-
insert(prev_sinks, source, sink, sinks).
Once you have this logic program, it is straightforward to translate it to
a GADT declaration:
type 's linkset =
| Init : ('source, 'sink) link -> ('source -> ('sink -> unit)) linkset
| Insert : ('source, 'sink) link * 'set1 linkset * ('set1, 'source,
'sink, 'set2) insert -> 'set2 linkset
and ('set1, 'source, 'sink, 'set2) insert =
| Here : (('sink -> _) as 'set1, 'source, 'sink, 'source -> 'set1) insert
| Next : ('set1, 'source, 'sink, 'set2) insert -> ('a -> 'set1, 'source,
'sink, 'a -> 'set2) insert
let _ = fun (type t1 t2 t3) (lnk1 : (t2, t1) link) (lnk2 : (t3, t1) link)
(lnk3 : (t3, t2) link) ->
let set = Init lnk1 in
let set = Insert (lnk2, set, Next Here) in
let set = Insert (lnk3, set, Here) in
set
On Wed, Sep 21, 2016 at 8:50 PM, Andre Nathan <andre@digirati.com.br> wrote:
> Em 21 de set de 2016, às 19:22, Jeremy Yallop <yallop@gmail.com> escreveu:
>
> It is! Here's one approach, which is based on two ideas:
>
>
> Wow, there's a lot to digest here :) Most of it is new to me, including
> the use of "evidence" types... I'll have to re-read it and try to work it
> out after a night of sleep.
>
> Thanks,
> Andre
>
>
[-- Attachment #2: Type: text/html, Size: 4041 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-09-30 13:54 ` Gabriel Scherer
@ 2016-10-05 19:46 ` Andre Nathan
2016-10-05 20:15 ` Daniel Bünzli
0 siblings, 1 reply; 9+ messages in thread
From: Andre Nathan @ 2016-10-05 19:46 UTC (permalink / raw)
To: caml-list
[-- Attachment #1.1: Type: text/plain, Size: 3814 bytes --]
Hi Gabriel
This is an interesting way to derive the GADT, at least for me, as I
have never did any logic programming. The resulting API is a bit easier
to use than the one from Jeremy's idea, at least for my use case.
I'm using this idea to experiment with a type-safe SQL query builder,
which so far looks like this:
from Team.table
|> belonging_to Team.owner Here
|> having_one Project.leader Here
|> select
|> fields [field User.id; field User.name] (Next Here)
|> fields [field Team.id; field Team.name] (Next (Next Here))
|> fields [all Project.table] Here
This generates the following query:
SELECT
users.id, users.name,
teams.id, teams.name,
projects.*
FROM
teams
LEFT JOIN
users ON users.id = teams.owner_id
LEFT JOIN
projects ON projects.leader_id = users.id
I'm not sure if this will ever be of use in practice, as the Next/Here
stuff might be too complicated for users, but overall I'm quite happy
with the result and what I've learned, though of course it's far from
complete (e.g. no support for WHERE clauses).
Thanks for the hints!
Andre
On 09/30/2016 10:54 AM, Gabriel Scherer wrote:
> I came back to this thread thanks to Alan Schmitt's Caml Weekly News.
>
> Here is a fairly systematic approach to the problem, contrasting
> Jeremy's more virtuoso approach. It works well when the whole set is
> statically defined in the source code, but I'm not sure about more
> dynamic scenarios (when a lot of the type variables involved become
> existentially quantified.
>
> The idea is to first formulate a solution using logic programming. A
> link set can be represented as a list of accepted sinks, and I can write
> a small Prolog program, "insert" that takes a new link, and a link set,
> and traverses the link set to find the link's sink in the list, and add
> the link's source to the list in passing.
>
> set([sink]) :- link(source, sink).
> set(sinks) :-
> set(prev_sinks),
> link(source, sink),
> insert(prev_sinks, source, sink, sinks).
>
> (* either the sink we want to insert is first in the list *)
> insert([sink | _], source, sink, [source, sink | sinks]).
>
> (* or it is to be found somewhere, recursively, under some other sink
> [s] *)
> insert([s | prev_sinks], source, sink, [s | sinks]) :-
> insert(prev_sinks, source, sink, sinks).
>
> Once you have this logic program, it is straightforward to translate it
> to a GADT declaration:
>
> type 's linkset =
> | Init : ('source, 'sink) link -> ('source -> ('sink -> unit)) linkset
> | Insert : ('source, 'sink) link * 'set1 linkset * ('set1, 'source,
> 'sink, 'set2) insert -> 'set2 linkset
> and ('set1, 'source, 'sink, 'set2) insert =
> | Here : (('sink -> _) as 'set1, 'source, 'sink, 'source -> 'set1) insert
> | Next : ('set1, 'source, 'sink, 'set2) insert -> ('a -> 'set1,
> 'source, 'sink, 'a -> 'set2) insert
>
> let _ = fun (type t1 t2 t3) (lnk1 : (t2, t1) link) (lnk2 : (t3, t1)
> link) (lnk3 : (t3, t2) link) ->
> let set = Init lnk1 in
> let set = Insert (lnk2, set, Next Here) in
> let set = Insert (lnk3, set, Here) in
> set
>
>
> On Wed, Sep 21, 2016 at 8:50 PM, Andre Nathan <andre@digirati.com.br
> <mailto:andre@digirati.com.br>> wrote:
>
> Em 21 de set de 2016, às 19:22, Jeremy Yallop <yallop@gmail.com
> <mailto:yallop@gmail.com>> escreveu:
>> It is! Here's one approach, which is based on two ideas:
>
> Wow, there's a lot to digest here :) Most of it is new to me,
> including the use of "evidence" types... I'll have to re-read it and
> try to work it out after a night of sleep.
>
> Thanks,
> Andre
>
>
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 473 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-10-05 19:46 ` Andre Nathan
@ 2016-10-05 20:15 ` Daniel Bünzli
0 siblings, 0 replies; 9+ messages in thread
From: Daniel Bünzli @ 2016-10-05 20:15 UTC (permalink / raw)
To: Andre Nathan; +Cc: caml-list
On Wednesday 5 October 2016 at 21:46, Andre Nathan wrote:
> I'm using this idea to experiment with a type-safe SQL query builder,
> which so far looks like this:
You may be interested in looking at this
http://okmij.org/ftp/meta-programming/#QUEL
Best,
Daniel
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-09-21 22:22 ` Jeremy Yallop
2016-09-22 0:50 ` Andre Nathan
@ 2016-10-07 17:02 ` Shayne Fletcher
2016-10-08 21:51 ` Shayne Fletcher
1 sibling, 1 reply; 9+ messages in thread
From: Shayne Fletcher @ 2016-10-07 17:02 UTC (permalink / raw)
To: Jeremy Yallop; +Cc: Andre Nathan, caml-list@inria.fr users
[-- Attachment #1: Type: text/plain, Size: 1677 bytes --]
On Wed, Sep 21, 2016 at 6:22 PM, Jeremy Yallop <yallop@gmail.com> wrote:
> I'll describe an interface based on these ideas that maintains the
> properties you stipulate. I'll leave the problem of building an
> implementation that satisfies the interface to you. (In fact, the
> interface is the tricky part and the implementation is straightforward
> if you can treat all the type parameters as phantom.)
>
How's this?
module M : S = struct
type _ sink = { name : string }
type _ source = { name : string }
type _ set = (string * string) list
type ('source, 'sink) link = ('source source * 'sink sink)
type ('sink, 'set) accepts =
| Accepts : ('sink, 'set) accepts
type 'sink fresh_set =
| Fresh_set : {
set : 't set;
accepts : ('sink, 't) accepts;
} -> 'sink fresh_set
let create_set (s : 'sink sink) : 'sink fresh_set =
Fresh_set { set = ([] : 't set);
accepts = (Accepts : ('sink, 't) accepts) }
type ('sink, 'parent) augmented_set =
| Augmented_set : {
set : 't set;
accepts: ('sink, 't) accepts;
cc : 's. ('s, 'parent) accepts -> ('s, 't) accepts
} -> ('sink, 'parent) augmented_set
let insert_link
(l : ('source, 'sink) link)
(s : 't set)
(a : ('sink, 't) accepts) : ('source, 't) augmented_set =
let ({name = src}, {name = dst}) = l in
Augmented_set {
set : 'tt set = (src, dst) :: s;
accepts = (Accepts : ('source, 'tt) accepts);
cc = fun (_ : (_, 't) accepts) -> (Accepts : (_, 't) accepts)
}
end
This melts my brain Jeremy! :)
--
Shayne Fletcher
[-- Attachment #2: Type: text/html, Size: 5339 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-10-07 17:02 ` Shayne Fletcher
@ 2016-10-08 21:51 ` Shayne Fletcher
2016-10-11 0:16 ` Jacques Garrigue
0 siblings, 1 reply; 9+ messages in thread
From: Shayne Fletcher @ 2016-10-08 21:51 UTC (permalink / raw)
To: Jeremy Yallop; +Cc: Andre Nathan, caml-list@inria.fr users
[-- Attachment #1: Type: text/plain, Size: 5922 bytes --]
On Fri, Oct 7, 2016 at 1:02 PM, Shayne Fletcher <
shayne.fletcher.50@gmail.com> wrote:
> On Wed, Sep 21, 2016 at 6:22 PM, Jeremy Yallop <yallop@gmail.com> wrote:
>
>> I'll describe an interface based on these ideas that maintains the
>> properties you stipulate. I'll leave the problem of building an
>> implementation that satisfies the interface to you. (In fact, the
>> interface is the tricky part and the implementation is straightforward
>> if you can treat all the type parameters as phantom.)
>>
>
> How's this?
>
> module M : S = struct
> ...
>
I've hit a problem with my attempt at an implementation actually :(
The issue is, the program below as written seems to work, but, if the type
[_ set] is made abstract in the signature [S], typing breaks. Can somebody
explain where I'm going wrong?
module type S = sig
type _ sink
type _ source
type _ set = (string * string) list (*Why can't this be abstract?*)
type ('source, 'sink) link
type ('sink, 'set) accepts
val mk_sink : string -> 'sink sink
val mk_source : string -> 'source source
val mk_link : 'source source * 'sink sink -> ('source, 'sink) link
type 'sink fresh_set =
| Fresh_set : {
set : 'set set;
accepts : ('sink, 'set) accepts;
} -> 'sink fresh_set
val create_set : 'sink sink -> 'sink fresh_set
type ('sink, 'parent) augmented_set =
| Augmented_set : {
set : 'set set;
accepts: ('sink, 'set) accepts;
cc : 's. ('s, 'parent) accepts -> ('s, 'set) accepts
} -> ('sink, 'parent) augmented_set
val insert_link :
('source, 'sink) link ->
'parent set ->
('sink, 'parent) accepts ->
('source, 'parent) augmented_set
end
module M : S = struct
type 'sink sink = { name : string }
type 'source source = { name : string }
type 'set set = (string * string) list
type ('source, 'sink) link = ('source source * 'sink sink)
let mk_sink (name : string) : 'sink sink = {name}
let mk_source (name : string) : 'source source = {name}
let mk_link ((source, sink) : 'source source * 'sink sink)
: ('source, 'sink) link = (source, sink)
type ('sink, 'set) accepts =
| Accepts : ('sink, 'set) accepts
type 'sink fresh_set =
| Fresh_set : {
set : 'set set;
accepts : ('sink, 'set) accepts;
} -> 'sink fresh_set
let create_set (s : 'sink sink) : 'sink fresh_set =
Fresh_set { set = ([] : 'set set);
accepts = (Accepts : ('sink, 'set) accepts) }
type ('sink, 'parent) augmented_set =
| Augmented_set : {
set : 't set;
accepts: ('sink, 't) accepts;
cc : 's. ('s, 'parent) accepts -> ('s, 't) accepts
} -> ('sink, 'parent) augmented_set
let insert_link
(l : ('source, 'sink) link)
(s : 'parent set)
(a : ('sink, 'parent) accepts) : ('source, 'parent) augmented_set =
let {name = src} : 'source source = fst l in
let {name = dst} : 'sink sink = snd l in
Augmented_set {
set : 't set = (src, dst) :: s;
accepts = (Accepts : ('source, 't) accepts);
cc = fun (_ : (_, 'parent) accepts) -> (Accepts : (_, 'parent)
accepts)
}
end
module Test (E : S) = struct
open E
type t1 and t2 and t3 and t4
let snk1 : t1 sink = mk_sink "sink1"
let snk2 : t2 sink = mk_sink "sink2"
let snk3 : t4 sink = mk_sink "sink3"
let src1 : t2 source = mk_source "source1"
let src2 : t3 source = mk_source "source2"
let link1 : (t2, t1) link = mk_link (src1, snk1) (*t2 src, t1 sink*)
let link2 : (t3, t1) link = mk_link (src2, snk1) (*t3 src, t1 sink*)
let link3 : (t3, t2) link = mk_link (src2, snk2) (*t3 src, t2 sink*)
let link4 : (t3, t4) link = mk_link (src2, snk3) (*t3 src, t4 sink*)
let test () =
(*Create a fresh set from a sink of type [t1]*)
let (Fresh_set {set; accepts = a} : t1 fresh_set) =
create_set snk1 in
(*
- [a] is evidence [set] accepts links with sink type [t1]
*)
(*Insert a [(t2, t1) link]*)
let Augmented_set
{set = set1; accepts = a1; cc = cc1} =
insert_link link1 set a in
(*
- [a1] is evidence [set1] accepts links with sink type [t2] ([t2] is
the source type of [link1])
- [cc] says that [set1] accepts links with sink types that its
parent [set] does:
- [cc1 a] provides evidence that says that [set1] will accept
[link2] which has sink type [t1] *)
(*Insert a [(t3, t1)] link*)
let Augmented_set
{set = set2; accepts = a2; cc = cc2} =
insert_link link2 set (cc1 a) in
(*
- [a2] says that [set2] accepts links with sink type [t3] ([t3] is
the source type of [link2])
- [cc2] says that [set2] accepts links with sink types that its
parent does:
- [cc2 a1] provides evidence that says that [set2] will accept
[link3] which has sink type [t2]
*)
(*Insert a [(t3, t2)] link*)
let (Augmented_set
{set = set3; accepts = a3; cc = cc3} : (t3, _) augmented_set) =
insert_link link3 set (cc2 a1) in
(*
- [a3] says that [set3] accepts links with sink type [t3] ([t3]is
the source type of [link3])
- [cc3] says that [set3] accepts links with sink types that its
parent does (that is, any links with sink types [t1], [t2] or [t3])
*)
(*There is just no way we can get insert [link4] into [set3]. The
is no evidence we can produce that will allow a link with sink
type [t4]. Try the below with any of [a1], [a2], [a3])*)
(*
let (Augmented_set
{set = set4; accepts = a4; cc = cc4} =
insert_link link4 set (cc3 a3) : (t3, _) augmented_set) in
*)
()
end
let _ = let module T = Test (M) in T.test ()
--
Shayne Fletcher
[-- Attachment #2: Type: text/html, Size: 19233 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Caml-list] Encoding "links" with the type system
2016-10-08 21:51 ` Shayne Fletcher
@ 2016-10-11 0:16 ` Jacques Garrigue
0 siblings, 0 replies; 9+ messages in thread
From: Jacques Garrigue @ 2016-10-11 0:16 UTC (permalink / raw)
To: Shayne Fletcher; +Cc: Jeremy Yallop, Andre Nathan, OCaML List Mailing
On 2016/10/09 06:51, Shayne Fletcher wrote:
>
> On Fri, Oct 7, 2016 at 1:02 PM, Shayne Fletcher <shayne.fletcher.50@gmail.com> wrote:
> On Wed, Sep 21, 2016 at 6:22 PM, Jeremy Yallop <yallop@gmail.com> wrote:
> I'll describe an interface based on these ideas that maintains the
> properties you stipulate. I'll leave the problem of building an
> implementation that satisfies the interface to you. (In fact, the
> interface is the tricky part and the implementation is straightforward
> if you can treat all the type parameters as phantom.)
>
> How's this?
>
> module M : S = struct
> ...
>
> I've hit a problem with my attempt at an implementation actually :(
>
> The issue is, the program below as written seems to work, but, if the type [_ set] is made abstract in the signature [S], typing breaks. Can somebody explain where I'm going wrong?
Making a type abstract loses some information (independently of hiding the precise definition):
* its uniqueness (i.e. it could be equal to any other type)
* its injectivity (i.e. whether "t1 t = t2 t” implies “t1 = t2”)
* its contractiveness (i.e. whether “t1 t” might actually be equal to to “t1”)
All of these properties have an impact of the behavior of type indices of GADTs.
If you want to use a type as GADT index, it is fine to hide the actual implementation, but it
is better to have a visible constructor.
For instance, you could define in the interface:
type set_impl
type _ set = private Set of set_impl (* private ensure that the type parameter is invariant *)
and in the implementation:
type set_impl = (string * string) list
type _ set = Set of set_impl
Jacques
> module type S = sig
>
> type _ sink
> type _ source
> type _ set = (string * string) list (*Why can't this be abstract?*)
> type ('source, 'sink) link
>
> type ('sink, 'set) accepts
>
> val mk_sink : string -> 'sink sink
> val mk_source : string -> 'source source
> val mk_link : 'source source * 'sink sink -> ('source, 'sink) link
>
> type 'sink fresh_set =
> | Fresh_set : {
> set : 'set set;
> accepts : ('sink, 'set) accepts;
> } -> 'sink fresh_set
>
> val create_set : 'sink sink -> 'sink fresh_set
>
> type ('sink, 'parent) augmented_set =
> | Augmented_set : {
> set : 'set set;
> accepts: ('sink, 'set) accepts;
> cc : 's. ('s, 'parent) accepts -> ('s, 'set) accepts
> } -> ('sink, 'parent) augmented_set
>
> val insert_link :
> ('source, 'sink) link ->
> 'parent set ->
> ('sink, 'parent) accepts ->
> ('source, 'parent) augmented_set
>
> end
>
> module M : S = struct
>
> type 'sink sink = { name : string }
> type 'source source = { name : string }
>
> type 'set set = (string * string) list
> type ('source, 'sink) link = ('source source * 'sink sink)
>
> let mk_sink (name : string) : 'sink sink = {name}
> let mk_source (name : string) : 'source source = {name}
> let mk_link ((source, sink) : 'source source * 'sink sink)
> : ('source, 'sink) link = (source, sink)
>
> type ('sink, 'set) accepts =
> | Accepts : ('sink, 'set) accepts
>
> type 'sink fresh_set =
> | Fresh_set : {
> set : 'set set;
> accepts : ('sink, 'set) accepts;
> } -> 'sink fresh_set
>
> let create_set (s : 'sink sink) : 'sink fresh_set =
> Fresh_set { set = ([] : 'set set);
> accepts = (Accepts : ('sink, 'set) accepts) }
>
> type ('sink, 'parent) augmented_set =
> | Augmented_set : {
> set : 't set;
> accepts: ('sink, 't) accepts;
> cc : 's. ('s, 'parent) accepts -> ('s, 't) accepts
> } -> ('sink, 'parent) augmented_set
>
> let insert_link
> (l : ('source, 'sink) link)
> (s : 'parent set)
> (a : ('sink, 'parent) accepts) : ('source, 'parent) augmented_set =
> let {name = src} : 'source source = fst l in
> let {name = dst} : 'sink sink = snd l in
> Augmented_set {
> set : 't set = (src, dst) :: s;
> accepts = (Accepts : ('source, 't) accepts);
> cc = fun (_ : (_, 'parent) accepts) -> (Accepts : (_, 'parent) accepts)
> }
>
> end
>
> module Test (E : S) = struct
>
> open E
>
> type t1 and t2 and t3 and t4
>
> let snk1 : t1 sink = mk_sink "sink1"
> let snk2 : t2 sink = mk_sink "sink2"
> let snk3 : t4 sink = mk_sink "sink3"
>
> let src1 : t2 source = mk_source "source1"
> let src2 : t3 source = mk_source "source2"
>
> let link1 : (t2, t1) link = mk_link (src1, snk1) (*t2 src, t1 sink*)
> let link2 : (t3, t1) link = mk_link (src2, snk1) (*t3 src, t1 sink*)
> let link3 : (t3, t2) link = mk_link (src2, snk2) (*t3 src, t2 sink*)
> let link4 : (t3, t4) link = mk_link (src2, snk3) (*t3 src, t4 sink*)
>
> let test () =
>
> (*Create a fresh set from a sink of type [t1]*)
> let (Fresh_set {set; accepts = a} : t1 fresh_set) =
> create_set snk1 in
> (*
> - [a] is evidence [set] accepts links with sink type [t1]
> *)
>
> (*Insert a [(t2, t1) link]*)
> let Augmented_set
> {set = set1; accepts = a1; cc = cc1} =
> insert_link link1 set a in
> (*
> - [a1] is evidence [set1] accepts links with sink type [t2] ([t2] is
> the source type of [link1])
> - [cc] says that [set1] accepts links with sink types that its
> parent [set] does:
> - [cc1 a] provides evidence that says that [set1] will accept
> [link2] which has sink type [t1] *)
>
> (*Insert a [(t3, t1)] link*)
> let Augmented_set
> {set = set2; accepts = a2; cc = cc2} =
> insert_link link2 set (cc1 a) in
> (*
> - [a2] says that [set2] accepts links with sink type [t3] ([t3] is
> the source type of [link2])
> - [cc2] says that [set2] accepts links with sink types that its
> parent does:
> - [cc2 a1] provides evidence that says that [set2] will accept
> [link3] which has sink type [t2]
> *)
>
> (*Insert a [(t3, t2)] link*)
> let (Augmented_set
> {set = set3; accepts = a3; cc = cc3} : (t3, _) augmented_set) =
> insert_link link3 set (cc2 a1) in
> (*
> - [a3] says that [set3] accepts links with sink type [t3] ([t3]is
> the source type of [link3])
> - [cc3] says that [set3] accepts links with sink types that its
> parent does (that is, any links with sink types [t1], [t2] or [t3])
> *)
>
> (*There is just no way we can get insert [link4] into [set3]. The
> is no evidence we can produce that will allow a link with sink
> type [t4]. Try the below with any of [a1], [a2], [a3])*)
> (*
> let (Augmented_set
> {set = set4; accepts = a4; cc = cc4} =
> insert_link link4 set (cc3 a3) : (t3, _) augmented_set) in
> *)
>
> ()
> end
>
> let _ = let module T = Test (M) in T.test ()
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2016-10-11 0:16 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-21 19:12 [Caml-list] Encoding "links" with the type system Andre Nathan
2016-09-21 22:22 ` Jeremy Yallop
2016-09-22 0:50 ` Andre Nathan
2016-09-30 13:54 ` Gabriel Scherer
2016-10-05 19:46 ` Andre Nathan
2016-10-05 20:15 ` Daniel Bünzli
2016-10-07 17:02 ` Shayne Fletcher
2016-10-08 21:51 ` Shayne Fletcher
2016-10-11 0:16 ` Jacques Garrigue
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox