WIP on rich return types. More complex than it seems.

This commit is contained in:
Georges Dupéron 2016-02-25 11:28:22 +01:00
parent 575ea6bd98
commit 713b637fbc
5 changed files with 312 additions and 29 deletions

View File

@ -49,27 +49,28 @@
(for-syntax syntax/parse))
(define-graph/multi-ctor gm ([a [b1 : b] [b2 : b] [s : String] [v : Number]]
[b [a1 : a] [s : String] [v : Number]])
[(r [v : Integer] [w : String])
: a
(printf "r ~a ~a\n" v w)
(a (bx (if (> v 0) (sub1 v) (string-length w)))
(by (if (> v 0) (sub1 v) (string-length w)) "xyz")
w
v)]
[(bx [v : Integer])
: b
(printf "bx ~a\n" v)
(b (r v "one") "x" v)]
[(by [v : Integer] [w : String])
: b
(printf "by ~a ~a\n" v w)
(b (r v "two") "y" (+ v (string-length w)))])
[b [a1 : a] [s : String] [v : Number]])
[(r [v : Integer] [w : String])
: a
(printf "r ~a ~a\n" v w)
(a (bx (if (> v 0) (sub1 v) (string-length w)))
(by (if (> v 0) (sub1 v) (string-length w)) "xyz")
w
v)]
[(bx [v : Integer])
: b
(printf "bx ~a\n" v)
(b (r v "one") "x" v)]
[(by [v : Integer] [w : String])
: b
(printf "by ~a ~a\n" v w)
(b (r v "two") "y" (+ v (string-length w)))])
(define gmi (gm 3 "b"))
(check-equal?: (get gmi v) 3)
(check-equal?: (get gmi b1 v) 2)
(check-equal?: (get gmi b1 s) "x")
(check-equal?: (get gmi b1 a1 v) 2)
;(check-equal?: (get gmi b1 a1 b1 a1 v) 1)
;(check-equal?: (get gmi b1 a1 b1 a1 b1 v) 1)

View File

@ -101,6 +101,7 @@ plain list.
@chunk[<graph-rich-return>
(define-syntax/parse <signature>
(define-temp-ids "first-step" name)
(define-temp-ids "first-step-expander2" name)
(define-temp-ids "~a/simple-mapping" (node ))
(define-temp-ids "~a/node" (mapping ))
(template
@ -126,15 +127,30 @@ encapsulating the result types of mappings.
@chunk[<first-pass-type-expander>
(define-type-expander (~> stx)
(syntax-parse stx
[(_ (~literal mapping))
[(_ (~datum mapping)) ;; TODO: should be ~literal
(template
(U (first-step #:placeholder mapping/node)
(tmpl-replace-in-type result-type
[node (first-step #:placeholder node)]
)))]
))]
;; TODO: should fall-back to outer definition of ~>, if any.
))
(define-type-expander (first-step-expander2 stx)
(syntax-parse stx
[(_ (~datum mapping)) ;; TODO: should be ~literal
(template
(U (first-step #:placeholder mapping/node)
(tmpl-replace-in-type result-type
[node (first-step #:placeholder node)]
)))]
;; TODO: should fall-back to outer definition of ~>, if any.
)
#;(U (first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street))))]
@; TODO: replace-in-type doesn't work well here, we need to define a
@; TODO: replace-in-type doesn't work wfell here, we need to define a
@; type-expander.
@chunk[<first-pass-field-type>
(tmpl-replace-in-type field-type
@ -237,27 +253,37 @@ encapsulating the result types of mappings.
(Street Street2/simple-mapping))
(map Street snames)))))))|#
(begin
#;(begin
(define-graph
first-step
#:definitions
((define-type-expander
(~> stx)
(syntax-parse stx
((_ (~literal m-cities))
((_ (~datum m-cities));(~literal m-cities))
(template (U
(first-step #:placeholder m-cities3/node)
(tmpl-replace-in-type
(Listof City)
(City (first-step #:placeholder City))
(Street (first-step #:placeholder Street))))))
((_ (~literal m-streets))
((_ (~datum m-streets));(~literal m-streets))
(template (U
(first-step #:placeholder m-streets4/node)
(tmpl-replace-in-type
(Listof Street)
(City (first-step #:placeholder City))
(Street (first-step #:placeholder Street)))))))))
(Street (first-step #:placeholder Street))))))))
(define-type-expander
(~~> stx)
(template (U
(first-step #:placeholder m-streets4/node)
(tmpl-replace-in-type
(Listof Street)
(City (first-step #:placeholder City))
(Street (first-step #:placeholder Street)))))
#;#'(U (first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street)))))
#|
(City [foo : Number] ((m1) (City 1)))
(Street [foo : Number] ((m2) (Street 2)))
@ -269,9 +295,9 @@ encapsulating the result types of mappings.
(City
(streets : (U m-streets4/node (Listof Street)))
((City1/simple-mapping
(streets : #|(~> m-streets)|#
(U (first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street)))
(streets : (~> m-streets);(Let [~> ~~>] (~> m-streets))
#|(U (first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street)))|#
))
(City streets)))
(Street
@ -327,6 +353,140 @@ encapsulating the result types of mappings.
(begin
(define-graph
first-step
#:definitions
((define-type-expander
(~> stx)
(syntax-parse
stx
((_ (~datum m-cities))
(template
(U
(first-step #:placeholder m-cities3/node)
(Listof (first-step #:placeholder City)))))
((_ (~datum m-streets))
(template
(U
(first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street)))))))
#;(define-type-expander
(first-step-expander2 stx)
(syntax-parse
stx
((_ (~literal m-cities))
(template
(U
(first-step #:placeholder m-cities3/node)
(Listof (first-step #:placeholder City)))))
((_ (~literal m-streets))
(template
(U
(first-step #:placeholder m-streets4/node)
(Listof (first-step #:placeholder Street)))))))
(define-type-expander
(~~> stx)
#;(template (U
(first-step #:placeholder m-streets4/node)
(tmpl-replace-in-type
(Listof Street)
(City (first-step #:placeholder City))
(Street (first-step #:placeholder Street)))))
#'(U m-streets4/node (Listof Street))))
(City
(streets : (Let [~> ~~>] (~> m-streets))#;(~> m-streets))
((City1/simple-mapping (streets : (~> m-streets))) (City streets)))
(Street
(sname : String)
((Street2/simple-mapping (sname : String)) (Street sname)))
(m-cities3/node
(returned : (Listof City))
((m-cities (cnames : (Listof (Listof String))))
(m-cities3/node
(let ((City City1/simple-mapping) (Street Street2/simple-mapping))
(define (strings→city (s : (Listof String))) (City (m-streets s)))
(map strings→city cnames)))))
(m-streets4/node
(returned : (Listof Street))
((m-streets (snames : (Listof String)))
(m-streets4/node
(let ((City City1/simple-mapping) (Street Street2/simple-mapping))
(map Street snames)))))))

View File

@ -0,0 +1,120 @@
#lang scribble/lp2
@(require "../lib/doc.rkt")
@doc-lib-setup
@title[#:style manual-doc-style]{Type and constructor
aliases in graph declarations}
@(table-of-contents)
@section{Introduction}
When declaring a graph, the names of its nodes and mappings
as well as those of the graph it is based on may collide. We
try here to provide reasonnable defaults indicating which
name should refer to what at each point.
@chunk[<example>
(graph g-old
[City [streets : (Listof Street)]]
[Street [name : String]]
mappings…)
(pass g-old g-new
([City [streets : (Listof Street)] [nstreets : Index]]
[Street [name : String]])
(m-city ([g g-old.City]) : City
(City g.streets (length g.streets))))]
Capitalization aside, the name @tc[city] could refer to seven different things:
@itemlist[
@item{The @racket[g-old.city] type}
@item{The incomplete @racket[g-new.city] type}
@item{The placeholder @racket[g-new.city] type}
@item{The @racket[g-new.city] type, but most of the time this one should be
needed only outside the graph declaration}
@item{The @racket[city] constructor, returning an incomplete node}
@item{The @racket[city] mapping, returning a placeholder}
@item{A @racket[city] field, but this one is not a problem, since it will
always be accessed via @racket[some-node-instance.city]}
@item{The return type of the @racket[city] mapping, as in @racket[~> city]}]
Furthermore, the @racket[city] constructor should accept several cases for
@tc[street], when giving the list of values for the @tc[streets] field:
@itemlist[
@item{A placeholder for @racket[street]}
@item{An incomplete @racket[street]}
@item{A @racket[g-old.street], which it will convert to the new type using the
implicit mapping}]
In the field types, we have one case:
@itemlist[
@item{The @racket[street] name, for example, should refer
to the with-promises type of the new graph @racket[g-new],
once the graph is fully constructed. When declaring the incomplete type for
@racket[city], @racket[street] will refer to the incomplete @racket[street],
but this is happening behind the scenes, and shouldn't cause any ambiguity.}]
In the mapping declaration, we have four cases:
@itemlist[
@item{Return type: this should obviously refer to the name
of the new node type (incomplete or placeholder).}
@item{Parameter type: it would be tempting to make node names there refer to
old node types. However, passing around placeholders and incomplete nodes then
becomes difficult.}
@item{Body, used as a function: inside the body, a node name should refer to
the constructor for the new node type, returning a placeholder.}
@item{Body, used as a type. A case where this could happen is when declaring an
inner function. The case isn't clear here, as the function could return an
incomplete node, or a placeholder. The same goes for the parameter type, or
the type of variables bound by let.}]
The type and constructor aspects are independant, as the
same identifier can be used both as a type and as a function
without ambiguity. It would however be more intuitive if the
return type of @tc[city] when used as a function was a
subtype of what @tc[city] expands to when used as a type, so
that @tc[(ann (city x y) city)] is always valid.
It is important to note that once constructed, incomplete nodes can be made
opaque without loss of functionality. In other words, we are free to use
@tc[(U incomplete placeholder)] as the type for all constructed nodes.
We could therefore use @tc[(U incomplete placeholder)] everywhere, except for
the parameters, where we need to access the old graph types, which may conflict
with the new ones.
When there is a single mapping for the node, we have, schematically:
@chunk[<single-mapping>
(g-old.city g-new.city
(begin
(city x y)
(bar other-old-city)))]
Where @tc[bar] is the constructor for another node taking a city for one of its
fields (here it will auto-call the mapping).
@section{Conclusion}
The solution we propose is to use @tc[(U incomplete placeholder)] everywhere for
the types, and disambiguate the old types in the parameters with @tc[old.city].
When used as a function, the name should refer to the constructor. We do not
have to worry about calling auto-defined mappings, as the constructors should
accept the old nodes, and convert them behind the scenes.
The only problem that remains, is when a user-defined mapping has the same name
as a node name, and has more than one argument (or doesn't accept a single old
node). This is probably something we can cope with, simply rejecting programs
that trigger this case, and request that the programmer uses different names for
the mapping and node.
@subsection{Shadowing}
We want to shadow the old @tc[city] type everywhere inside the graph
declaration. Otherwise this will lead to inconsistencies, inside an inner
definition for example. We do not want however the name of the nodes to leak
outside as definitions, as this would conflict with other graphs having the same
node names.
@chunk[<*>
(begin)]

View File

@ -325,7 +325,7 @@ arguments, tagged with the @tc[node]'s name):
@; TODO: maybe replace node types with placeholder types
@chunk[<define-placeholder-type>
(struct (A) node/placeholder-struct ([f : A]))
(struct (A) node/placeholder-struct ([f : A]) #:transparent)
(define-type node/placeholder-type
(node/placeholder-struct (List param-type )))]
@ -351,7 +351,7 @@ indicates at which index in the queue's results the successor can be found.
@; TODO: use a type-expander here, instead of a template metafunction.
@CHUNK[<define-with-indices>
(struct node/index-type ([i : Index]))
(struct node/index-type ([i : Index]) #:transparent)
(define-type node/with-indices-type
(List 'node/with-indices-tag <field/with-indices-type> ))

View File

@ -151,7 +151,9 @@ else.
#,(expand-type #'T (bind-type-vars #'(TVar ...) env)))]
[((~literal Rec) R:id T:expr)
#`(Rec R #,(expand-type #'T (bind-type-vars #'(R) env)))]
[((~literal Let) [V:id E:id] T:expr)
[((~datum Let) [V:id E:id] T:expr);; TODO: ~literal instead of ~datum
;; TODO: ~commit when we find Let, so that syntax errors are not
;; interpreted as an arbitrary call.
;; TODO : for now we only allow aliasing (which means E is an id),
;; not on-the-fly declaration of type expanders. This would require
;; us to (expand) them.