Started writeup about graph macro.

This commit is contained in:
Georges Dupéron 2015-11-16 18:49:21 +01:00
parent 5ed7ddb7c6
commit 7e931314d1
3 changed files with 206 additions and 23 deletions

View File

@ -8,27 +8,196 @@
@section{Introduction}
This module provides a @tc[graph] macro which helps constructing immutable
graphs (using lambdas to defer potentially cyclic references).
@subsection{The graph's type}
Each node type in the graph is a variant's constructor, tagged with the node
name. For example, a graph representing a city and its inhabitants could use
these variants:
@chunk[<example-variants>
[City (Listof Street) (Listof Person)]
[Street (Listof House)]
[House Person Street]
[Person String]]
Notice the cycle in the type: a street contains houses, which are located on the
same street.
@subsection{Example usage}
@subsubsection{A seed from which to unravel the graph: the root parameters}
In order to build a graph with that type, we start from the root parameters.
Here, we will take a representation of the city as a list of
@tc[(street . person-name)] pairs, and will convert it to a more convenient
graph representation. Our single root parameter will thus be the whole list:
@chunk[<example-root>
'(["Amy" . "Ada street"]
["Jack" . "J street"]
["Anabella" . "Ada street"])]
We then provide a mapping from the root parameter to the root node, in our case
@tc[City]. When processing the root parameter, one can call mappings that will
create other nodes.
@subsubsection{Mapping the root parameters to the root node}
Here is the root mapping for our example. It calls the @tc[street] and
@tc[person] mappings.
@chunk[<example-mappings>
[(city [c : (Listof (Pairof String String))]) : City
(City (remove-duplicates (map ( (curry street c) car) c))
(remove-duplicates (map ( Person cdr) c)))]]
@subsubsection{More mappings}
Next, we write the @tc[street] mapping, which takes a street name and the whole
city @tc[c] in list form, and creates a @tc[Street] node.
@chunk[<example-mappings>
[(street [c : (Listof (Pairof String String))] [s : String]) : Street
(Street (map ( (curry house s c) car)
(filter (λ ([x : (Pairof String String)])
(equal? (cdr x) s))
c)))]]
The @tc[house] mapping calls back the @tc[street] mapping, to store for each
house a reference to the containing street. Normally, this would cause infinite
recursion in an eager language, like @tc[typed/racket]. However, the mappings
aren't called directly, and instead the @tc[street] function here returns a
placeholder. This allows us to not worry about mutually recursive mappings: a
mapping can be called any number of times with the same data, it will actually
only be run once.
The @tc[make-graph-constructor] macro will post-process the result of each
mapping, and replace the placeholders with promises for the the result of the
mapping. The promises are not available during graph construction, so there is
no risk of forcing one before it is available.
Finally, we write the @tc[house] mapping.
@chunk[<example-mappings>
[(house [s : String] [c : (Listof (Pairof String String))] [p : String])
: House
(House (Person p) (street c s))]]
Notice how we are calling directly the @tc[Person] constructor above. We also
called it directly in the @tc[city] mapping. Since @tc[Person] does not contain
references to @tc[House], @tc[Street] or @tc[City], we do not need to delay
creation of these nodes by calling yet another mapping.
@; TODO: above: Should we merge two identical instances of Person? They won't
@; necessarily be eq? if they contain cycles deeper in their structure, anyway.
@; And we are already merging all equal? link-requests, so there shouldn't be
@; any blowup in the number of nodes.
@; It would probably be better for graph-map etc. to have all the nodes in the
@; database, though.
The number and names of mappings do not necessarily reflect the graph's type.
Here, we have no mapping named @tc[person], because that node is always created
directly. Conversely, we could have two mappings, @tc[big-street] and
@tc[small-street], with different behaviours, instead of passing an extra
boolean argument to @tc[street].
@subsubsection{Making a constructor for the graph}
@chunk[<make-constructor-example>
(make-graph-constructor (<example-variants>)
<example-root>
<example-mappings>)]
@subsubsection{Creating a graph instance}
@chunk[<use-example>
(define g <make-constructor-example>)]
@subsection{More details on the semantics}
Let's take a second look at the root mapping:
@chunk[<example-mappings-2>
[(city [c : (Listof (Pairof String String))])
(City (remove-duplicates (map ( (curry street c) car) c))
(remove-duplicates (map ( Person cdr) c)))]]
The first case shows that ce can use @tc[street] as any other function, passing
it to @tc[curry], and calling remove-duplicates on the results. Note that reach
placeholder returned by @tc[street] will contain all information passed to it,
here a street name and @tc[c]. Two placeholders for @tc[street] will therefore
be @tc[equal?] if and only if all the arguments passed to @tc[street] are
@tc[equal?]. The placeholders also include a symbol specifying which mapping was
called, so two placeholders for two mappings called with identical parameters
will not be @tc[equal?].
The second case shows that we can also directly call the constructor for the
@tc[Person] node type. If that type contains references to other nodes, the
constructor here will actually accept either a placeholder, or an actual
instance, which itself may contain placeholders.
The node type allowing placeholders is derived from the ideal type given above.
Here, the type for @tc[Person] is @tc[[String]], so there are no substitutions
to make. On the contrary, the type for @tc[City], originally expressed as
@tc[[(Listof Street) (Listof Person)]], will be rewritten into
@tc[[(Listof (U Street Street-Placeholder))
(Listof (U Person Person-Placeholder))]].
The @tc[rewrite-type] module we use to derive types with placeholders from the
original ones only handles a handful of the types offered by @tc[typed/racket].
In particular, it does not handle recursive types described with @tc[Rec] yet.
@section{Implementation}
In this section, we will describe how the @tc[make-graph-constructor] macro is
implemented.
@subsection{The macro's syntax}
We use a simple syntax for @tc[make-graph-constructor], and make it more
flexible through wrapper macros.
@chunk[<signature>
(make-graph-constructor
[node type ...]
root:expr
[(mapping [param (~literal :) param-type] ...) (~literal :) result-type
body]
...)]
@chunk[<make-graph-constructor>
(define-syntax/parse <signature>
#'(void))]
@section{Conclusion}
@chunk[<module-main>
(module main typed/racket
(require (for-syntax syntax/parse
racket/syntax
"../lib/low-untyped.rkt")
"../lib/low.rkt")
(provide make-graph-constructor)
<make-graph-constructor>)]
@chunk[<module-test>
(module* test typed/racket
(require (submod "..")
typed/rackunit)
<make-constructor-example>
(require (submod ".." doc)))]
@chunk[<*>
(begin
(module main typed/racket
(require (for-syntax syntax/parse
racket/syntax
"../lib/low-untyped.rkt")
"../lib/low.rkt")
)
<module-main>
(require 'main)
(provide (all-from-out 'main))
(module* test typed/racket
(require (submod "..")
typed/rackunit)
(require (submod ".." doc))))]
<module-test>)]

View File

@ -258,7 +258,7 @@ to the queue).
(λ (e acc)
(let ([new-tagged ((inst set (Pairof Element Tag)))])
<mutable-get-tag-for>
(% result new-acc = (process (car e) acc get-tag-for)
(% result new-acc = (process (car e) acc mget-tag-for)
(values result new-acc new-tagged))))
(cons ((inst hash Element Result)) ((inst hash Tag Result)))
(λ (e r racc)
@ -268,7 +268,7 @@ to the queue).
(values ht-element ht-tag last-accumulator)))]
@chunk[<mutable-get-tag-for>
(define (get-tag-for [x : Element] [acc : Accumulator])
(define (mget-tag-for [x : Element] [acc : Accumulator])
(if (hash-has-key? all-tags x)
(values (hash-ref all-tags x) acc)
(% tag new-acc = (make-tag x acc)
@ -297,7 +297,10 @@ provider, which uses an opaque database type @tc[X] to know for which elements
was a tag requested.
@chunk[<fold-queue-sets-immutable-tags-process-type>
( (X) ( Element Accumulator X ( Element Accumulator X (Values Tag Accumulator X))
( (X) ( Element
Accumulator
X
( Element Accumulator X (Values Tag Accumulator X))
(Values Result Accumulator X)))]
@chunk[<inst-fold-queue-sets-immutable-tags>
@ -342,10 +345,15 @@ was a tag requested.
(values ht-element ht-tag result-acc)))]
@chunk[<immutable-get-tag-for>
(define% (get-tag-for [x : Element]
[acc : Accumulator]
[(h . s) : (Pairof (HashTable Element Tag)
(Setof (Pairof Element Tag)))])
(: get-tag-for ( Element
Accumulator
(Pairof (HashTable Element Tag)
(Setof (Pairof Element Tag)))
(values Tag
Accumulator
(Pairof (HashTable Element Tag)
(Setof (Pairof Element Tag))))))
(define% (get-tag-for x acc (h . s))
(if (hash-has-key? h x)
(values (hash-ref h x)
acc

View File

@ -713,15 +713,21 @@
#:with (tmp) (generate-temporaries #'(x))
#:with var-type #`[tmp : type]
#:with (expanded ...) #'([x.expanded tmp]))
(pattern x:%pat
(pattern x:id
#:with var-type #'x
#:with (expanded ...) #'())))
#:with (expanded ...) #'())
(pattern x:%pat
#:with (tmp) (generate-temporaries #'(x))
#:with var-type #'tmp
#:with (expanded ...) #'([x.expanded tmp]))))
(define-syntax (define% stx)
(syntax-parse stx
[(_ (name param:typed-pat ...)
(~and (~seq ret ...) (~optional (~seq (~literal :) ret-type)))
. body)
(displayln #'(define (name param.var-type ...)
(match-let (param.expanded ... ...) ret ... . body)))
#'(define (name param.var-type ...)
(match-let (param.expanded ... ...) ret ... . body))]))