www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

tagged-structure-low-level.hl.rkt (81883B)


      1 #lang hyper-literate typed/racket/base #:no-require-lang #:no-auto-require
      2 @(require scribble-enhanced/doc
      3           racket/require
      4           hyper-literate
      5           (for-label (except-in phc-toolkit ?)
      6                      phc-toolkit/untyped-only
      7                      racket/list
      8                      racket/format
      9                      racket/promise
     10                      racket/string
     11                      racket/require
     12                      racket/set
     13                      remember
     14                      syntax/parse
     15                      typed-struct-props
     16                      typed/racket/unsafe
     17                      (subtract-in racket/contract typed/racket/base)
     18                      (subtract-in racket/syntax phc-toolkit)
     19                      (subtract-in syntax/stx phc-toolkit)
     20                      (except-in (subtract-in typed/racket/base
     21                                              racket/set)
     22                                 values)
     23                      (only-in racket/base values)
     24                      "node-low-level.hl.rkt"))
     25 @(unless-preexpanding
     26   (require (for-label (submod ".." sorting-and-identifiers)))
     27   (require (for-label (submod ".." pre-declare)))
     28   (require (for-label (submod ".."))))
     29 @doc-lib-setup
     30 
     31 @title[#:style manual-doc-style
     32        #:tag "tagged-low-level"
     33        #:tag-prefix "phc-adt/tagged-low-level"
     34        ]{Low-level implementation of tagged structures}
     35 
     36 @(chunks-toc-prefix
     37   '("(lib phc-adt/scribblings/phc-adt-implementation.scrbl)"
     38     "phc-adt/tagged-low-level"))
     39 
     40 @(declare-exporting (lib "phc-adt/tagged-structure-low-level.hl.rkt"))
     41 
     42 @(table-of-contents)
     43 
     44 @section{Overview}
     45 
     46 A tagged structure is a data structure associating fields with their value.
     47 Two tagged structure types with the same set of fields can be distinguished by
     48 their tag. Compared to the traditional algebraic data types, a tagged
     49 structure acts like (traditional) structure wrapped in a (traditional)
     50 constructor.
     51 
     52 Tagged structures are the central data type of this library.
     53 @itemlist[
     54  @item{Tagged structures can be used as-is.}
     55  @item{Constructors which tag multiple values can be created by aggregating
     56   those values, and storing them within a tagged structure containing a single
     57   field named ``@racketid[values]''.}
     58  @item{Untagged structures can be created by implicitly supplying a default tag,
     59   which is the same for all untagged structures. In our case, the default tag is
     60   named @racket[untagged].}
     61  @item{Nodes are implemented exactly like tagged structures, except that the
     62   contents of their fields are wrapped in promises. The promises allow creating
     63   data structures that contain cycles in appearance, despite being built
     64   exclusively using purely immutable primitives.}]
     65 
     66 In order to implement field access in a way that works for tagged structures
     67 and nodes alike, it is desirable that their implementation has the same shape.
     68 We therefore also wrap the contents of tagged structure fields with promises.
     69 While the promises present within nodes do perform some kind of computation
     70 each time they are forced, the promises present within tagged structures
     71 simply return an already-known value.
     72 
     73 @section{Implementation using Racket structs}
     74 
     75 A tagged structure is implemented as a Racket struct, in which every field has a
     76 distinct polymorphic type.
     77 
     78 @chunk[<define-tagged>
     79        (struct/props (fieldᵢ/τ …) tagged-struct common-struct ()
     80                      #:property prop:custom-write
     81                      (make-writer common-struct name fieldᵢ …)
     82                
     83                      #:property prop:equal+hash
     84                      (make-comparer common-struct tagged-struct name
     85                                     fieldᵢ …))]
     86 
     87 Tagged structures with different tag names but the same set of fields are
     88 implemented as descendant @racket[struct]s of a common one. The common
     89 @racket[struct] contains all the fields, and the descendants only serve to
     90 distinguish between the different tag names.
     91 
     92 @chunk[<define-common>
     93        (struct/props (fieldᵢ/τ …) common-struct TaggedTop-struct
     94                      ([fieldᵢ : (Promise fieldᵢ/τ)] …))]
     95 
     96 It is desirable that all data structures (tagged structures and nodes) have
     97 the same shape. This makes it easier to access the value of a given field,
     98 without having two different field access operators (one for tagged structure
     99 and one for nodes). Since nodes need to have the contents of each field
    100 wrapped within a @racket[Promise], we will also impose this on tagged
    101 structures and their derivatives (untagged structures and constructors).
    102 Although the promises used in nodes will actually perform some work, the
    103 promises in other data structures will simply wrap an already-computed value.
    104 The operator accessing a field's contents will therefore access the desired
    105 field, and force the promise contained within, in order to obtain the real
    106 value.
    107 
    108 @subsection{Nodes as subtypes of their corresponding tagged struct type}
    109 
    110 Nodes are implemented as subtypes of their corresponding tagged struct type.
    111 
    112 @chunk[<define-node>
    113        (struct/props (fieldᵢ/τ … raw-D/τ raw-I/τ)
    114                      node-struct
    115                      tagged-struct
    116                      ([raw : (raw-node raw-D/τ raw-I/τ)])
    117                      #:property prop:custom-write
    118                      (make-node-writer common-struct
    119                                        name
    120                                        fieldᵢ …)
    121                      #:property prop:equal+hash
    122                      (make-node-comparer common-struct
    123                                          node-struct
    124                                          name
    125                                          fieldᵢ …))]
    126 
    127 They contain an extra @racket[raw] field, which contains a raw representation of
    128 the node consisting of a tuple of two elements: the graph's database of nodes,
    129 and an index into that database).
    130 
    131 @racketblock[
    132  (struct (Database) raw-node ([database : Database] [index : Index]))]
    133 
    134 @section{Common ancestor to all tagged structures: @racket[TaggedTop-struct]}
    135 
    136 @chunk[#:save-as taggedtop-decl <TaggedTop>
    137        (struct TaggedTop-struct () #:transparent)]
    138 
    139 @defstruct[TaggedTop-struct ()]{
    140  We define the @racket[TaggedTop-struct] struct as the parent of every
    141  ``common'' struct.
    142  
    143  @(taggedtop-decl)
    144 
    145  The hierarchy is therefore as follows:
    146 
    147  @itemlist[
    148  @item{The @racket[struct] for a node is a subtype of the @racket[struct] for
    149    the tagged structure with the same name and fields.}
    150  @item{The @racket[struct] for a tagged structure is a subtype of the ``common''
    151    @racket[struct] which has the same set of fields. All tagged structures with
    152    the same fields but distinct tag names are implemented as subtypes of their
    153    ``common'' @racket[struct].}
    154  @item{@racket[TaggedTop-struct] is the direct supertype of all ``common''
    155    @racket[struct]. Transitively, @racket[TaggedTop-struct] is therefore also a
    156    supertype of the @racket[struct]s corresponding to every tagged structure and
    157    node.}]}
    158 
    159 @section{Printing and comparing structures and nodes}
    160 
    161 The data types defined in this library have a custom printed representation, and
    162 have a custom implementation of equality.
    163 
    164 The following sections present how tagged structures are printed and compared.
    165 Nodes are described in a separate section,
    166 @secref["node-low-level" #:tag-prefixes '("phc-adt/node-low-level")]. Their
    167 behaviour differs slightly from how tagged structures are printed and
    168 compared, as they need to take into account the presence of logical cycles in
    169 the data structure. Node printing is explained in the section
    170 @secref["Printing_nodes" #:tag-prefixes '("phc-adt/node-low-level")], and
    171 node equality is explained in the section
    172 @secref["Comparing_and_hashing_nodes"
    173         #:tag-prefixes '("phc-adt/node-low-level")].
    174 
    175 @subsection{Printing tagged structures}
    176 
    177 Tagged structures are printed in different ways depending on their fields:
    178 
    179 @itemlist[
    180  @item{If the tagged structure only contains a single field whose name is
    181   ``@racketid[values]'', then it is printed as
    182   @racket[(constructor name value …)].}
    183 
    184  @item{Otherwise, if the tagged structure's tag name is @racket[untagged],
    185   it is printed as @racket[(structure name [field value] …)].}
    186 
    187  @item{Finally, it the tagged structure does not fall in the above two cases,
    188   it is printed as @racket[(tagged name [field value] …)].}]
    189 
    190 @CHUNK[<custom-write>
    191        (define-syntax/parse (make-writer pid name fieldᵢ …)
    192          (define fields (map syntax-e (syntax->list #'(fieldᵢ …))))
    193          (define has-values-field? (member 'values fields))
    194          (define has-other-fields? (not (null? (remove 'values fields))))
    195          (define untagged? (eq? (syntax-e #'name) 'untagged))
    196 
    197          (define/with-syntax e
    198            (cond
    199              [untagged?
    200               #'(format "(structure ~a)"
    201                         (string-join (list <format-field> …) " "))]
    202              [(and has-values-field? (not has-other-fields?))
    203               #'`(constructor name
    204                               . ,(force ((struct-accessor pid values) self)))]
    205              [else
    206               #'(format "(tagged ~a ~a)"
    207                         'name
    208                         (string-join (list <format-field> …) " "))]))
    209 
    210          #'(λ (self out mode)
    211              (display e out)))]
    212 
    213 Each field is formatted as @tc[[fieldᵢ valueᵢ]]. The whole printed form is
    214 built so that copy-pasting it yields a value which is @racket[equal?] to the
    215 original.
    216 
    217 @chunk[<format-field>
    218        (format "[~a ~a]" 'fieldᵢ (force ((struct-accessor pid fieldᵢ) self)))]
    219 
    220 @section{Comparing tagged structures}
    221 
    222 Tagged structures are compared by recursively applying @racket[equal?] to their
    223 fields, after forcing the promise wrapping each field. Forcing these promises is
    224 safe, as the result of these promises is already known when creating the tagged
    225 structure. The promises are present only to ensure that tagged structures and
    226 nodes have the same shape, but cannot by themselves create logical cycles.
    227 
    228 @CHUNK[<equal+hash>
    229        (define-syntax/parse (make-comparer pid id name fieldᵢ …)
    230          #'(list (λ (a b rec-equal?)
    231                    (and ((struct-predicate id) a)
    232                         ((struct-predicate id) b)
    233                         (rec-equal? (force ((struct-accessor pid fieldᵢ) a))
    234                                     (force ((struct-accessor pid fieldᵢ) b)))
    235    236                         #t))
    237                  (λ (a rec-hash)
    238                    (bitwise-xor (rec-hash 'id)
    239                                 (rec-hash (force ((struct-accessor pid fieldᵢ) a)))
    240                                 …))
    241                  (λ (a rec-hash)
    242                    (bitwise-xor (rec-hash 'id)
    243                                 (rec-hash (force ((struct-accessor pid fieldᵢ) a)))
    244                                 …))))]
    245 
    246 @section{Pre-declaring structs}
    247 
    248 @subsection{Why pre-declare the structs?}
    249 
    250 We wish to pre-declare a Racket @tc[struct] type for all tagged structures used
    251 in the program. This requirement is needed to achieve several goals:
    252 
    253 @itemlist[
    254  @item{To allow on-the-fly declaration. Otherwise, it would be necessary to be
    255   in a @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{module-begin
    256    context} to be able to declare a @racket[struct].@note{It is possible in
    257    untyped Racket to declare a struct within an 
    258    @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{
    259     internal-definition context}, however it is not possible in Typed Racket due
    260    to
    261    @hyperlink["https://github.com/racket/typed-racket/issues/192"]{bug #192}.
    262    Furthermore, the declaration would not be visible outside the @racket[let].}
    263   This means that, within an expression, it would be impossible to create an
    264   instance of a structure which was not previously declared.}
    265  @item{To enable "interned" tagged structures, i.e. two tagged structures with
    266   the same name and fields used in two different files are compatible, just as
    267   @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{prefab} structs.}
    268  @item{If we use @code{(get-field s b)} in module @code{A}, and define a 
    269   @racket[struct] type with a field @code{b} in module @code{B}, then the module
    270   @code{A} would have to @racket[require] @code{B}, in order to have access to
    271   the struct metadata, and this could easily create cyclic dependencies.
    272   
    273   Moving the @racket[struct] definition to a third file solves that problem.}]
    274 
    275 We do not however wish to remember the type of each field. Indeed, the type may
    276 contain type identifiers which are not exported by the module using the tagged
    277 structure. Instead, we declare parametric structs, using a distinct type
    278 argument for each field. The struct can then be instantiated with the correct
    279 types where needed.
    280 
    281 @CHUNK[<pre-declare-all-tagged-structure-structs>
    282        (define-syntax (pre-declare-all-tagged-structure-structs stx)
    283          (define/with-parse (([name₁:id fieldᵢ:id …] [nameⱼ:id . _] …) …)
    284            (group-by (∘ list->set cdr)
    285                      <all-remembered-tagged-structures>
    286                      set=?))
    287          #`(begin
    288              (require (submod (lib "phc-adt/tagged-structure-low-level.hl.rkt")
    289                               pre-declare)
    290                       phc-toolkit)
    291              (pre-declare-group [name₁ nameⱼ …] [fieldᵢ …])
    292              …))]
    293 
    294 @CHUNK[<pre-declare-all-tagged-structure-structs>
    295        (define-syntax/parse (pre-declare-group [name:id …] [fieldᵢ:id …])
    296          
    297          (define/with-syntax common-struct
    298            (make-struct-identifier-common #f #'(fieldᵢ …)))
    299          
    300          (define-temp-ids "~a/τ" (fieldᵢ …))
    301 
    302          #'(begin
    303              <define-common>
    304              (provide (struct-out common-struct))
    305              
    306              (pre-declare-tagged-and-node common-struct name [fieldᵢ …])
    307              …))]
    308 
    309 @CHUNK[<pre-declare-all-tagged-structure-structs>
    310        (define-syntax/case
    311            (pre-declare-tagged-and-node common-struct name (fieldᵢ …)) ()
    312          
    313          (define-temp-ids "~a/τ" (fieldᵢ …))
    314          (define-temp-ids "~a/pred" (fieldᵢ …))
    315          (define/with-syntax ([_ . Anyᵢ] …) #'([fieldᵢ . Any] …))
    316          (define/with-syntax tagged-struct
    317            (make-struct-identifier-tagged #f #'(name fieldᵢ …)))
    318          (define/with-syntax tagged-pred
    319            (make-struct-identifier-tagged-pred #f #'(name fieldᵢ …)))
    320          (define/with-syntax node-struct
    321            (make-struct-identifier-node #f #'(name fieldᵢ …)))
    322 
    323          (template (begin
    324                      <define-tagged>
    325                      <define-tagged-pred>
    326                      <define-node>
    327                      (provide tagged-pred
    328                               (struct-out tagged-struct)
    329                               (struct-out node-struct)))))]
    330 
    331 @subsection{Remembering tagged structures across compilations}
    332 
    333 In order to know which @tc[struct]s to pre-declare, we need
    334 to remember them across compilations. We use the 
    335 @tc[remember] library for that purpose.
    336 
    337 @chunk[<all-remembered-tagged-structures>
    338        (set->list (begin (check-adt-context)
    339                          (get-remembered 'tagged-structure)))]
    340 
    341 @chunk[<remember-structure!>
    342        (remember-write! 'tagged-structure
    343                         `(,(syntax-e #'name) . ,sorted-field-symbols))]
    344 
    345 @deftogether[
    346  ([defform #:kind "for-syntax function"
    347    (check-remembered-common! #'(name fieldᵢ …))]
    348   [defform #:kind "for-syntax function"
    349    (check-remembered-tagged! #'(name fieldᵢ …))]
    350   [defform #:kind "for-syntax function"
    351    (check-remembered-node! #'(name fieldᵢ …))])]{
    352  These for-syntax functions check whether a tagged structure with the given name
    353  and fields has already been remembered, and return the common, tagged or node
    354  @racket[struct] identifier for that tagged structure. If the tagged structure
    355  has not yet been remembered, or if it was remembered for the first time during
    356  the current compilation, a delayed error is raised, and the function returns
    357  the @racket[struct] identifier for the @racket[not-remembered] tagged
    358  structure as a fallback, so that the current compilation may proceed as far as
    359  possible before the delayed error is triggered. The @racket[not-remembered]
    360  tagged structure has no fields, and is always available.
    361 
    362  The delayed error asks the user to re-compile the file, as new items have been
    363  remembered. The delayed error will be displayed after the file is expanded, but
    364  before it is type checked. If another compilation error happens while compiling
    365  the rest of the file, then the delayed error will not be displayed.}
    366 
    367 @defform*[#:kind "for-syntax function"
    368           [(check-remembered-?! #'(name fieldᵢ …))]]{
    369  This for-syntax function checks whether a tagged structure with the given name
    370  and fields has already been remembered, and returns @racket[#t] in that case.
    371  If the tagged structure has not yet been remembered, or if it was remembered
    372  for the first time during the current compilation, a delayed error is raised
    373  and the function returns @racket[#f].}
    374 
    375 If the name and set of fields were already remembered, all is fine and
    376 we simply generate the corresponding @tc[struct] identifiers:
    377 
    378 @chunk[<check-remembered!>
    379        (define-for-syntax/case-args (check-remembered! (name fieldᵢ …))
    380          (let* ([sorted-fields (sort-fields #'(fieldᵢ …))]
    381                 [sorted-field-symbols (map syntax-e sorted-fields)])
    382            (when (check-duplicates sorted-field-symbols)
    383              (raise-syntax-error 'tagged-structure
    384                                  "Duplicate fields in structure descriptor"
    385                                  #f
    386                                  #f
    387                                  sorted-fields))
    388            (check-adt-context)
    389            (if (remembered? 'tagged-structure `(,(syntax-e #'name)
    390                                                 . ,sorted-field-symbols))
    391                (values
    392                 #t
    393                 (make-struct-identifier-common #t sorted-fields)
    394                 (make-struct-identifier-tagged #t `(,#'name . ,sorted-fields))
    395                 (make-struct-identifier-node #t `(,#'name . ,sorted-fields)))
    396                <not-remembered>)))]
    397        
    398 @chunk[<check-remembered!>
    399        (define-for-syntax (check-remembered-common! descriptor)
    400          (let-values ([(? common tagged node) (check-remembered! descriptor)])
    401            common))
    402        (define-for-syntax (check-remembered-tagged! descriptor)
    403          (let-values ([(? common tagged node) (check-remembered! descriptor)])
    404            tagged))
    405        (define-for-syntax (check-remembered-node! descriptor)
    406          (let-values ([(? common tagged node) (check-remembered! descriptor)])
    407            node))
    408        (define-for-syntax (check-remembered-?! descriptor)
    409          (let-values ([(? common tagged node) (check-remembered! descriptor)])
    410            ?))]
    411 
    412 The @tc[struct] identifiers are generated as shown below.
    413 Since their identifier is of the form 
    414 @tc["(structure field₀ field₁ …)"], it contains the unusual
    415 characters @tc["("] and @tc[")"]. This reduces the risk of
    416 conflicts between @racket[struct] identifiers produced by
    417 this library and user-declared identifiers (the structs
    418 declared by this library normally have a fresh scope, but
    419 due to bug #399 this is currently not possible).
    420 
    421 @CHUNK[<make-struct-identifier-from-list>
    422        (define/contract? (make-struct-identifier-from-list ctx-introduce? lst)
    423          (-> boolean?
    424              (listof symbol?)
    425              identifier?)
    426 
    427          ((if ctx-introduce? ctx-introduce syntax-local-introduce)
    428           #`#,(string->symbol
    429                (~a lst))))]
    430 
    431 @CHUNK[<make-struct-identifier-common>
    432        (define/contract? (make-struct-identifier-common ctx-introduce? fields)
    433          (-> boolean?
    434              (stx-list/c (listof identifier?))
    435              identifier?)
    436          
    437          (make-struct-identifier-from-list
    438           ctx-introduce?
    439           `(common . ,(map syntax-e (sort-fields fields)))))]
    440        
    441 @CHUNK[<make-struct-identifier-tagged>
    442        (define/contract? (make-struct-identifier-tagged ctx-introduce?
    443                                                         name+fields)
    444          (-> boolean?
    445              (stx-list/c (cons/c identifier? (listof identifier?)))
    446              identifier?)
    447          
    448          (make-struct-identifier-from-list
    449           ctx-introduce?
    450           `(tagged ,(syntax-e (stx-car name+fields))
    451                    . ,(map syntax-e
    452                            (sort-fields (stx-cdr name+fields))))))]
    453 
    454 @CHUNK[<make-struct-identifier-node>
    455        (define/contract? (make-struct-identifier-node ctx-introduce?
    456                                                       name+fields)
    457          (-> boolean?
    458              (stx-list/c (cons/c identifier? (listof identifier?)))
    459              identifier?)
    460 
    461          (make-struct-identifier-from-list
    462           ctx-introduce?
    463           `(node ,(syntax-e (stx-car name+fields))
    464                  . ,(map syntax-e
    465                          (sort-fields (stx-cdr name+fields))))))]
    466 
    467 @CHUNK[<make-struct-identifier-tagged-pred>
    468        (define/contract?
    469            (make-struct-identifier-tagged-pred ctx-introduce?
    470                                                name+fields)
    471          (-> boolean?
    472              (stx-list/c (cons/c identifier? (listof identifier?)))
    473              identifier?)
    474          
    475          (make-struct-identifier-from-list
    476           ctx-introduce?
    477           `(tagged-cast-predicate
    478             ,(syntax-e (stx-car name+fields))
    479             . ,(map syntax-e
    480                     (sort-fields (stx-cdr name+fields))))))]
    481 
    482 @subsection{Sorting the set of fields}
    483 
    484 Some operations will need to obtain the Racket @tc[struct]
    485 for a given set of fields. The fields are first sorted, in
    486 order to obtain a canonical specification for the structure.
    487 
    488 @chunk[<sort-fields>
    489        (define/contract? (sort-fields fields)
    490          (-> (stx-list/c (listof identifier?))
    491              (listof identifier?))
    492          
    493          (when (check-duplicates (stx->list fields) #:key syntax-e)
    494            (raise-syntax-error 'tagged-structure
    495                                "Duplicate fields in structure descriptor"
    496                                fields))
    497          (sort (stx->list fields)
    498                symbol<?
    499                #:key syntax-e))]
    500 
    501 The @tc[sort-fields-alist] function will sort an associative
    502 list where the keys are field identifiers. This allows us
    503 later to sort a list of fields associated with their type,
    504 for example.
    505 
    506 @chunk[<sort-fields-alist>
    507        (define/contract? (sort-fields-alist fields-alist)
    508          (-> (stx-list/c (listof (stx-car/c identifier?)))
    509              (listof (stx-e/c (cons/c identifier? any/c))))
    510          
    511          (when (check-duplicates (map (λ~> stx-car stx-e)
    512                                       (stx->list fields-alist)))
    513            (raise-syntax-error 'structure
    514                                "Duplicate fields in structure description"
    515                                (stx-map stx-car fields-alist)))
    516          (sort (stx->list fields-alist)
    517                symbol<?
    518                #:key (λ~> stx-car stx-e)))]
    519 
    520 @subsection{Not-yet-remembered structs should cause an error}
    521 
    522 If the set of fields given to @tc[check-remember-structure!] is not already
    523 known, it is remembered (i.e. written to a file by the
    524 @racketmodname[remember] library), so that it will be known during the next
    525 compilation. A delayed error is then set up, and a dummy @tc[struct]
    526 identifier is returned (the struct identifier associated with the tagged
    527 structure @racket[not-remembered], which does not have any field).
    528 
    529 @chunk[<not-remembered>
    530        (begin <remember-structure!>
    531               (remembered-error! 'tagged-structure
    532                                  #'(name fieldᵢ …)
    533                                  (syntax->list #'(name fieldᵢ …)))
    534               (values
    535                #f
    536                (make-struct-identifier-common #t '())
    537                (make-struct-identifier-tagged #t `(,#'not-remembered))
    538                (make-struct-identifier-node #t `(,#'not-remembered))))]
    539 
    540 The structure with no fields is pre-remembered so that it
    541 is always available and can be returned in place of the
    542 actual @tc[struct] when the requested set of fields has not
    543 been remembered yet:
    544 
    545 @chunk[<remember-empty-tagged-structure>
    546        (remembered! tagged-structure (not-remembered))]
    547 
    548 Our goal is to let the file be macro-expanded as much as
    549 possible before an error is triggered. That way, if the file
    550 contains multiple structures which have not yet been
    551 remembered, they can all be remembered in one compilation
    552 pass, instead of stumbling on each one in turn.
    553 
    554 We use the @racket[not-remembered] tagged structure as a fallback when a
    555 structure is not already remembered. This is semantically incorrect, and
    556 obviously would not typecheck, as the user code would expect a different type.
    557 However, the delayed error is triggered @emph{before} the type checker has a
    558 chance to run: the type checker runs on the fully-expanded program, and the
    559 error is triggered while the program is still being macro-expanded.
    560 
    561 The compilation may however fail earlier. For example, if a
    562 reflective operation attempts to obtain a @tc[struct]'s
    563 accessor for a given field, but that @tc[struct] corresponds
    564 to a structure which was not yet remembered, then this
    565 operation will fail at compile-time. All the primitive
    566 operations implemented in this file should however work even
    567 if the structure wasn't remembered, giving results which
    568 will not typecheck but can still be expanded.
    569 
    570 We additionally always declare a tagged structure with only the
    571 ``@racketid[values]'' field, as it is the base type for all constructors.
    572 
    573 @chunk[<remember-one-constructor>
    574        (remembered! tagged-structure (always-remembered values))]
    575 
    576 @section{Creating instances of a tagged structure}
    577 
    578 @defform[#:kind "for-syntax function"
    579          #:literals (:)
    580          (tagged-builder! #'(name [fieldᵢ τᵢ] …))
    581          #:grammar ([name Identifier]
    582                     [tvarᵢ Identifier]
    583                     [fieldᵢ Identifier]
    584                     [τᵢ Type])]{
    585  This for-syntax function returns the syntax for a builder function for the
    586  given tagged structure. The builder function expects one parameter of type
    587  @racket[τᵢ] for each @racket[fieldᵢ].
    588 
    589  The builder function has the following type:
    590 
    591  @racketblock[(→ τᵢ … (tagged name [fieldᵢ τᵢ] …))]
    592 
    593  where @racket[(tagged name [fieldᵢ τᵢ] …)] is the type produced by:
    594 
    595  @racketblock[(tagged-type! #'(name [fieldᵢ τᵢ] …))]
    596 
    597  This function also checks that a tag with the given name and fields has already
    598  been remembered, using @racket[check-remembered-tagged!]}
    599 
    600 @CHUNK[<tagged-builder!>
    601        (define-for-syntax tagged-builder!
    602          (λ/syntax-case (name [fieldᵢ τᵢ] …) ()
    603            (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    604            (define/with-syntax ([sorted-fieldⱼ . sorted-τⱼ] …)
    605              (sort-fields-alist #'([fieldᵢ . τᵢ] …)))
    606            (cond
    607              (code:comment "Can't use (inst st …) on a non-polymorphic type.")
    608              [(stx-null? #'(fieldᵢ …))
    609               #'st]
    610              (code:comment "Otherwise, re-order")
    611              [else
    612               #`(λ ([fieldᵢ : τᵢ] …)
    613                   ((inst st sorted-τⱼ …) (delay sorted-fieldⱼ) …))])))]
    614 
    615 @defform[#:kind "for-syntax function"
    616          #:literals (:)
    617          (tagged-∀-builder! #'((tvarᵢ ...) name [fieldᵢ τᵢ] …))
    618          #:grammar ([name Identifier]
    619                     [fieldᵢ Identifier]
    620                     [tvarᵢ Identifier]
    621                     [τᵢ Type])]{
    622  This for-syntax function returns the syntax for a polymorphic builder function
    623  for the given tagged structure. The polymorphic builder function has the given
    624  @racket[tvarᵢ] type variables. The polymorphic builder function expects one
    625  parameter of type @racket[τᵢ] for each @racket[fieldᵢ], where @racket[τᵢ] can
    626  be a regular type or one of the @racket[tvarᵢ] type variables.
    627 
    628  The builder function has the following type:
    629 
    630  @RACKETBLOCK[(∀ (tvarᵢ …) (→ τᵢ … (tagged name [fieldᵢ τᵢ] …)))]
    631 
    632  where @racket[(tagged name [fieldᵢ τᵢ] …)] is the type produced by:
    633 
    634  @racketblock[(tagged-type! #'(name [fieldᵢ τᵢ] …))]
    635  
    636  This function also checks that a tag with the given name and fields has already
    637  been remembered, using @racket[check-remembered-tagged!]}
    638 
    639 @CHUNK[<tagged-∀-builder!>
    640        (define-for-syntax tagged-∀-builder!
    641          (λ/syntax-case ((tvarᵢ …) name [fieldᵢ τᵢ] …) ()
    642            (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    643            (define/with-syntax ([sorted-fieldⱼ . sorted-τⱼ] …)
    644              (sort-fields-alist #'([fieldᵢ . τᵢ] …)))
    645            (cond
    646              [(stx-null? #'(tvarᵢ …))
    647               (tagged-builder! #'(name [fieldᵢ τᵢ] …))]
    648              (code:comment "Can't use (inst st …) on a non-polymorphic type.")
    649              [(stx-null? #'(fieldᵢ …))
    650               #`(λ #:∀ (tvarᵢ …) () (st))]
    651              (code:comment "Otherwise, re-order")
    652              [else
    653               #`(λ #:∀ (tvarᵢ …) ([fieldᵢ : τᵢ] …)
    654                   ((inst st sorted-τⱼ …) (delay sorted-fieldⱼ) …))])))]
    655 
    656 @defform[#:kind "for-syntax function"
    657          (tagged-infer-builder! #'(name fieldᵢ …))
    658          #:grammar ([name Identifier]
    659                     [fieldᵢ Identifier])]{
    660  This for-syntax function returns the syntax for a polymorphic builder function
    661  for the given tagged structure. The polymorphic builder function has one type
    662  variable for each field. The polymorphic builder function expects one parameter
    663  for each @racket[fieldᵢ], and infers the type of that field.
    664 
    665  The builder function has the following type:
    666 
    667  @RACKETBLOCK[(∀ (τᵢ …) (→ τᵢ … (tagged name [fieldᵢ τᵢ] …)))]
    668 
    669  where @racket[(tagged name [fieldᵢ τᵢ] …)] is the type produced by:
    670 
    671  @racketblock[(tagged-type! #'(name [fieldᵢ τᵢ] …))]
    672 
    673  with a fresh @racket[τᵢ] identifier is introduced for each @racket[fieldᵢ].
    674  
    675  This function also checks that a tag with the given name and fields has already
    676  been remembered, using @racket[check-remembered-tagged!]}
    677 
    678 @CHUNK[<tagged-infer-builder!>
    679        (define-for-syntax tagged-infer-builder!
    680          (λ/syntax-case (name fieldᵢ …) ()
    681            (define-temp-ids "~a/τ" (fieldᵢ …))
    682            (tagged-∀-builder! #'((fieldᵢ/τ …) name [fieldᵢ fieldᵢ/τ] …))))]
    683 
    684 @section{Predicate for a tagged structure}
    685 
    686 @defform[#:kind "for-syntax function"
    687          #:literals (:)
    688          (tagged-any-predicate! #'(name fieldᵢ …))
    689          #:grammar ([name Identifier]
    690                     [fieldᵢ Identifier])]{
    691  This for-syntax function returns the syntax for a predicate for the given
    692  tagged structure. No check is performed on the contents of the structure's
    693  fields, i.e. the predicate has the following type:
    694 
    695  @RACKETBLOCK[(→ Any Boolean : (tagged name [fieldᵢ Any] …))]
    696 
    697  where @racket[(tagged name [fieldᵢ Any] …)] is the type produced by:
    698 
    699  @racketblock[(tagged-type! #'(name [fieldᵢ Any] …))]
    700 
    701  In other words, it is a function accepting any value, and returning
    702  @racket[#t] if and only if the value is an instance of a structure with the
    703  given tag name and fields, regardless of the contents of those fields.
    704  Otherwise, @racket[#f] is returned.
    705  
    706  This function also checks that a tag with the given name and
    707  fields has already been remembered, using @racket[check-remembered-tagged!]}
    708 
    709 @chunk[<tagged-any-predicate!>
    710        (define-for-syntax/case-args (tagged-any-predicate! (name fieldᵢ …))
    711          (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    712          (define/with-syntax ([_ . Anyᵢ] …) #'([fieldᵢ . Any] …))
    713          #'(make-predicate (maybe-apply-type st Anyᵢ …)))]
    714 
    715 @defform[#:kind "for-syntax function"
    716          #:literals (:)
    717          (tagged-any-fields-predicate #'name)
    718          #:grammar ([name Identifier])]{
    719  This for-syntax function returns the syntax for a predicate for any tagged
    720  structure with the given name. No check is performed on the structure's
    721  fields.}
    722 
    723 @chunk[<tagged-any-fields>
    724        (define-for-syntax tagged-any-fields
    725          (λ/syntax-parse tag-name:id
    726            (map (λ (name+fields)
    727                   (with-syntax ([(name fieldᵢ …) name+fields])
    728                     (cons (check-remembered-tagged! #'(name fieldᵢ …))
    729                           name+fields)))
    730                 (filter (λ (name+fields) (equal? (car name+fields)
    731                                                  (syntax-e #'tag-name)))
    732                         <all-remembered-tagged-structures>))))]
    733 
    734 @CHUNK[<tagged-any-fields-predicate>
    735        (define-for-syntax tagged-any-fields-predicate
    736          (λ/syntax-parse tag-name:id
    737            #`(make-predicate #,(tagged-any-fields-type #'tag-name))))]
    738 
    739 @subsection{A predicate over the contents of the fields}
    740 
    741 @defform[#:kind "for-syntax function"
    742          #:literals (:)
    743          (tagged-predicate! #'(name [fieldᵢ τᵢ] …))
    744          #:grammar ([name Identifier]
    745                     [fieldᵢ Identifier]
    746                     [τᵢ Type])]{
    747  This for-syntax function returns the syntax for a predicate for the given
    748  tagged structure. The predicate also checks that each  @racket[fieldᵢ] is a
    749  value of the corresponding @racket[τᵢ] type. Each given @racket[τᵢ] must be
    750  a suitable argument for Typed Racket's  @racket[make-predicate].
    751 
    752  The predicate has the following type:
    753 
    754  @RACKETBLOCK[(→ Any Boolean : (tagged name [fieldᵢ τᵢ] …))]
    755 
    756  where @racket[(tagged name [fieldᵢ τᵢ] …)] is the type produced by:
    757 
    758  @racketblock[(tagged-type! #'(name [fieldᵢ τᵢ] …))]
    759 
    760  In other words, it is a function accepting any value, and returning @racket[#t]
    761  if and only if the value is an instance of a structure with the given tag and
    762  fields, and each @racket[fieldᵢ] contains a value of the type @racket[τᵢ].
    763  Otherwise, @racket[#f] is returned. Note that the actual values contained
    764  within the fields are checked, instead of their static type (supplied or
    765  inferred when building the tagged structure instance).
    766  
    767  This function also checks that a tag with the given name and
    768  fields has already been remembered, using @racket[check-remembered-tagged!].}
    769 
    770 Typed Racket's @racket[make-predicate] cannot operate on promises, because its
    771 automatic contract generation would need to force the promise. This is a
    772 potentially side-effectful operation that a predicate should not perform
    773 automatically. In our case, we know that by construction the promises are side
    774 effect-free. We therefore manually define a predicate builder. The returned
    775 predicate forces the promises contained within each @racket[fieldᵢ], and
    776 checks whether the resulting value is of the corresponding type @racket[τᵢ]:
    777 
    778 @chunk[<tagged-pred-lambda>
    779        (λ (fieldᵢ/pred …)
    780          (λ ([v : Any])
    781            (and ((struct-predicate tagged-struct) v)
    782                 (fieldᵢ/pred (force ((struct-accessor common-struct fieldᵢ) v)))
    783                 …)))]
    784 
    785 Unfortunately, Typed Racket's inference is not strong enough to properly
    786 express the type of the predicate we build above; as of the time of writing
    787 this library, it infers that when the predicate returns @racket[#true],
    788 @racket[v] has the @racket[(tagged-struct Anyᵢ …)] type, and that its fields
    789 have the respective @racket[fieldᵢ/τ] type. It also infers that when the
    790 predicate returns false, one of these propositions must be false@note{These
    791  negative propositions cannot be written with the syntax currently supported by
    792  Typed Racket, but they are still shown by Typed Racket for debugging purposes
    793  in error messages, for example when trying to annotate the function with an
    794  incorrect proposition.}. However, it is not currently capable of combining
    795 these pieces of information into a single proposition asserting that the type
    796 of @racket[v] is @racket[(tagged-struct fieldᵢ/τ …)] if and only if the
    797 predicate returns true. To circumvent this precision problem, we annotate the
    798 predicate builder defined above with the most precise type that can be
    799 expressed and automatically validated by Typed Racket:
    800 
    801 @chunk[<tagged-pred-simple-type>
    802        (∀ (fieldᵢ/τ …)
    803           (→ (→ Any Boolean : fieldᵢ/τ)
    804    805              (→ Any Boolean : #:+ (!maybe-apply tagged-struct Anyᵢ …))))]
    806 
    807 We then use @racket[unsafe-cast]@note{It would be tempting to use the safe
    808  @racket[cast], but @racket[cast] enforces the type with a contract, which, in
    809  this case, cannot be generated by the current version of Typed Racket.} to
    810 give the predicate the more precise type:
    811 
    812 @chunk[<tagged-pred-correct-type>
    813        (∀ (fieldᵢ/τ …)
    814           (→ (→ Any Any : fieldᵢ/τ)
    815    816              (→ Any Boolean : (!maybe-apply tagged-struct fieldᵢ/τ …))))]
    817 
    818 @chunk[<define-tagged-pred>
    819        (define tagged-pred
    820          (unsafe-cast/no-expand (ann <tagged-pred-lambda>
    821                                      <tagged-pred-simple-type>)
    822                                 <tagged-pred-correct-type>))]
    823 
    824 Finally, we can define the @racket[tagged-predicate!] for-syntax function
    825 described earlier in terms of this specialised predicate builder.
    826 
    827 @; TODO: use a special make-predicate that recognizes other tagged
    828 @; structure, so that a predicate for a tagged structure can reference
    829 @; other tagged structures. Take care of cycles for nodes.
    830 @chunk[<tagged-predicate!>
    831        (define-for-syntax/case-args (tagged-predicate! (name [fieldᵢ τᵢ] …))
    832          (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    833          (define/with-syntax ([sorted-fieldⱼ . sorted-τⱼ] …)
    834            (sort-fields-alist #'([fieldᵢ . τᵢ] …)))
    835          (define/with-syntax st-make-predicate
    836            (make-struct-identifier-tagged-pred #t #'(name fieldᵢ …)))
    837          #'(st-make-predicate (make-predicate sorted-τⱼ) …))]
    838 
    839 @defform[#:kind "for-syntax function"
    840          #:literals (:)
    841          (tagged-pred-predicate! #'(name [fieldᵢ predᵢ] …))
    842          #:grammar ([name Identifier]
    843                     [fieldᵢ Identifier]
    844                     [predᵢ (ExpressionOf (→ Any Any : τᵢ))])]{
    845  This for-syntax function returns the syntax for a predicate for the given
    846  tagged structure. The predicate also checks that each @racket[fieldᵢ] is
    847  accepted by the corresponding predicate @racket[predᵢ].
    848 
    849  When the type of a given @racket[predᵢ] includes a filter @racket[: τᵢ]
    850  asserting that it returns true if and only if the value is of type
    851  @racket[τᵢ], then the predicate produced by @racket[tagged-predicate!] will
    852  also have that filter on the corresponding field. By default, a function of
    853  type @racket[(→ Any Any)] will implicitly have the @racket[Any] filter, which
    854  does not bring any extra information. In other words, the @racket[(→ Any Any)]
    855  type in which no filter is specified is equivalent to the
    856  @racket[(→ Any Any : Any)] type, where @racket[: Any] indicates the filter.
    857 
    858  The generated predicate has therefore the following type:
    859 
    860  @RACKETBLOCK[(→ Any Boolean : (tagged name [fieldᵢ τᵢ] …))]
    861 
    862  where @racket[(tagged name [fieldᵢ τᵢ] …)] is the type produced by:
    863 
    864  @racketblock[(tagged-type! #'(name [fieldᵢ τᵢ] …))]
    865 
    866  In other words, it is a function accepting any value, and returning @racket[#t]
    867  if and only if the value is an instance of a structure with the given tag and
    868  fields, and each @racket[fieldᵢ] contains a value of the type @racket[τᵢ].
    869  Otherwise, @racket[#f] is returned. Note that the actual values contained
    870  within the fields are checked, instead of their static type (supplied or
    871  inferred when building the tagged structure instance).
    872  
    873  This function also checks that a tag with the given name and
    874  fields has already been remembered, using @racket[check-remembered-tagged!].}
    875 
    876 @chunk[<tagged-pred-predicate!>
    877        (define-for-syntax/case-args
    878            (tagged-pred-predicate! (name [fieldᵢ predᵢ] …))
    879          (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    880          (define/with-syntax ([sorted-fieldⱼ . sorted-predⱼ] …)
    881            (sort-fields-alist #'([fieldᵢ . predᵢ] …)))
    882          (define/with-syntax st-make-predicate
    883            (make-struct-identifier-tagged-pred #t #'(name fieldᵢ …)))
    884          #'(st-make-predicate sorted-predⱼ …))]
    885 
    886 @section{Matching against tagged structures}
    887 
    888 @defform[#:kind "for-syntax function"
    889          #:literals (:)
    890          (tagged-match! #'(name [fieldᵢ patᵢ] …))
    891          #:grammar ([name Identifier]
    892                     [fieldᵢ Identifier]
    893                     [patᵢ Match-Pattern])]{
    894  This for-syntax function returns the syntax for a match pattern for the given
    895  tagged structure. The pattern matches each @racket[fieldᵢ] against the
    896  corresponding @racket[patᵢ]. It also checks that a tag with the given name and
    897  fields has already been remembered, using @racket[check-remembered-tagged!]}
    898 
    899 @chunk[<tagged-match!>
    900        (define-for-syntax/case-args (tagged-match! (name [fieldᵢ patᵢ] …))
    901          (define-values (was-remembered common-struct tagged-struct node-struct)
    902            (check-remembered! #'(name fieldᵢ …)))
    903          (define/with-syntax st tagged-struct)
    904          (define/with-syntax ([sorted-fieldⱼ . sorted-patⱼ] …)
    905            (sort-fields-alist #'([fieldᵢ . patᵢ] …)))
    906          (if was-remembered
    907              #'(struct st ((app force sorted-patⱼ) …))
    908              <match-not-remembered>))]
    909 
    910 The match pattern @tc[(struct st (pat …))] fails to compile when the struct
    911 @tc[st] is not declared, and when it does not have the right number of fields.
    912 To avoid a confusing error message when the tagged structure was not
    913 remembered yet, we insert a dummy pattern but still process the nested
    914 patterns. This way, the nested patterns can themselves raise not-remembered
    915 errors and cause new tagged structures to be remembered.
    916 
    917 @chunk[<match-not-remembered>
    918        #'(app (λ (v) 'not-remembered) (and sorted-patⱼ …))]
    919 
    920 @defform[#:kind "for-syntax function"
    921          #:literals (:)
    922          (tagged-anytag-match! #'([fieldᵢ patᵢ] …))
    923          #:grammar ([fieldᵢ Identifier]
    924                     [patᵢ Match-Pattern])]{
    925                                            
    926  This for-syntax function returns the syntax for a match pattern for any
    927  tagged structure with the given fields, regardless of the tagged structure's
    928  tag. The pattern matches each @racket[fieldᵢ] against the corresponding
    929  @racket[patᵢ]. It also checks that a tag with a dummy name (@racket[any-tag])
    930  and the given fields has already been remembered, using
    931  @racket[check-remembered-tagged!]}
    932 
    933 @; TODO: get rid of the any-tag
    934 
    935 @chunk[<tagged-anytag-match!>
    936        (define-for-syntax/case-args (tagged-anytag-match! ([fieldᵢ patᵢ] …))
    937          (define-values (was-remembered common-struct tagged-struct node-struct)
    938            (check-remembered-tagged! #'(any-tag fieldᵢ …)))
    939          (define/with-syntax st common-struct)
    940          (define/with-syntax ([sorted-fieldⱼ . sorted-patⱼ] …)
    941            (sort-fields-alist #'([fieldᵢ . patᵢ] …)))
    942          (if was-remembered
    943              #'(struct st ((app force sorted-patⱼ) …))
    944              <match-not-remembered>))]
    945 
    946 @section{Type of a tagged structure}
    947 
    948 @defform[#:kind "for-syntax function"
    949          #:literals (:)
    950          (tagged-type! #'(name [fieldᵢ τᵢ] …))
    951          #:grammar ([name Identifier]
    952                     [fieldᵢ Identifier])]{
    953  This for-syntax function returns the syntax for the type of tagged structures
    954  with the given name and field types. It also checks that a tag with the given
    955  name and fields has already been remembered, using
    956  @racket[check-remembered-tagged!]}
    957 
    958 @chunk[<tagged-type!>
    959        (define-for-syntax tagged-type!
    960          (λ/syntax-case (name [fieldᵢ τᵢ] …) ()
    961            (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    962            (define/with-syntax ([sorted-fieldⱼ . sorted-τⱼ] …)
    963              (sort-fields-alist #'([fieldᵢ . τᵢ] …)))
    964            (code:comment "Can't instantiate a non-polymorphic type.")
    965            (if (stx-null? #'(fieldᵢ …))
    966                #'st
    967                #'(st sorted-τⱼ …))))]
    968 
    969 @defform[#:kind "for-syntax function"
    970          #:literals (:)
    971          (tagged-∀-type! #'((tvarᵢ …) name [fieldᵢ τᵢ] …))
    972          #:grammar ([name Identifier]
    973                     [fieldᵢ Identifier])]{
    974  This for-syntax function returns the syntax for a polymorphic type for the
    975  given tagged structure, using the given type variables @racket[tvarᵢ…]. It also
    976  checks that a tag with the given name and fields has already been remembered,
    977  using @racket[check-remembered-tagged!]}
    978 
    979 @CHUNK[<tagged-∀-type!>
    980        (define-for-syntax tagged-∀-type!
    981          (λ/syntax-case ((tvarᵢ …) name [fieldᵢ τᵢ] …) ()
    982            (define/with-syntax st (check-remembered-tagged! #'(name fieldᵢ …)))
    983            (define/with-syntax ([sorted-fieldⱼ . sorted-τⱼ] …)
    984              (sort-fields-alist #'([fieldᵢ . τᵢ] …)))
    985            (cond
    986              [(stx-null? #'(tvarᵢ …))
    987               (tagged-type! #'(name [fieldᵢ τᵢ] …))]
    988              (code:comment "Can't instantiate a non-polymorphic type.")
    989              [(stx-null? #'(fieldᵢ …))
    990               #`(∀ (tvarᵢ …) st)]
    991              (code:comment "Otherwise, re-order")
    992              [else
    993               #`(∀ (tvarᵢ …) (st sorted-τⱼ …))])))]
    994 
    995 @defform[#:kind "for-syntax function"
    996          #:literals (:)
    997          (tagged-infer-type! #'(name fieldᵢ …))
    998          #:grammar ([name Identifier]
    999                     [fieldᵢ Identifier])]{
   1000  This for-syntax function returns the syntax for a polymorphic type for the
   1001  given tagged structure, with one automatically-generated type variable per
   1002  field. It also checks that a tag with the given name and fields has already
   1003  been remembered, using @racket[check-remembered-tagged!]}
   1004 
   1005 @chunk[<tagged-infer-type!>
   1006        (define-for-syntax tagged-infer-type!
   1007          (λ/syntax-case (name fieldᵢ …) ()
   1008            (define-temp-ids "~a/τ" (fieldᵢ …))
   1009            (tagged-∀-type! #'((fieldᵢ/τ …) name [fieldᵢ fieldᵢ/τ] …))))]
   1010 
   1011 @defform[#:kind "for-syntax function"
   1012          #:literals (:)
   1013          (tagged-any-fields-type #'name)
   1014          #:grammar ([name Identifier])]{
   1015  This for-syntax function returns the syntax for the union type of all tagged
   1016  structures with the given name. The type of each field is @racket[Any].}
   1017 
   1018 @CHUNK[<tagged-any-fields-type>
   1019        (define-for-syntax tagged-any-fields-type
   1020          (λ/syntax-parse tag-name:id
   1021            (define/with-syntax ([sᵢ nameᵢ fieldᵢⱼ …] …)
   1022              (tagged-any-fields #'tag-name))
   1023            (define/with-syntax ([[_ Anyᵢⱼ] …] …)
   1024              #'([[fieldᵢⱼ Any] …] …))
   1025            #`(U . #,(stx-map (λ (sᵢ Anyᵢⱼ*) (if (stx-null? Anyᵢⱼ*)
   1026                                                 sᵢ
   1027                                                 #`(#,sᵢ . #,Anyᵢⱼ*)))
   1028                              #'(sᵢ …)
   1029                              #'([Anyᵢⱼ …] …)))))]
   1030 
   1031 @section{Accessing fields of tagged structures}
   1032 
   1033 @defform[(tagged-get-field v f)]{
   1034  Returns the value contained within the @racket[f] field of the tagged
   1035  structure instance @racket[v]. }
   1036 
   1037 @CHUNK[<tagged-get-field>
   1038        (define-syntax (tagged-get-field stx)
   1039          (syntax-case stx ()
   1040            [(_ v f . else-expr)
   1041             (identifier? #'f)
   1042             (let ()
   1043               (define/with-syntax else-expr-or-error
   1044                 (syntax-case #'else-expr ()
   1045                   [() (if (identifier? #'v)
   1046                           #`(typecheck-fail #,stx #:covered-id v)
   1047                           #`(typecheck-fail #,stx))]
   1048                   [(e) #'e]))
   1049               (define/with-syntax ([sⱼ all-fieldⱼₖ …] …)
   1050                 (has-fields/common #'(f)))
   1051               #'(let ([v-cache v])
   1052                   (cond
   1053                     [((struct-predicate sⱼ) v-cache)
   1054                      (force ((struct-accessor sⱼ f) v))]
   1055   1056                     [else else-expr-or-error])))]))]
   1057 
   1058 @defform[(λ-tagged-get-field f)]{
   1059  Returns an accessor for the @racket[f] field of any tagged structure instance.
   1060  The instance must contain a field named @racket[f], otherwise a type error is
   1061  raised at compile-time, when using the accessor on an inappropriate value.
   1062 }
   1063 
   1064 @CHUNK[<λ-tagged-get-field>
   1065        (define-syntax/parse (λ-tagged-get-field f:id)
   1066          (define/with-syntax ([sⱼ all-fieldⱼₖ …] …)
   1067            (has-fields/common #'(f)))
   1068          #`(λ #:∀ (τ) ([v : #,(has-fields/type #'([f τ]))])
   1069              (cond [((struct-predicate sⱼ) v)
   1070                     (force ((struct-accessor sⱼ f) v))]
   1071                    …)))]
   1072 
   1073 @section{Row polymorphism}
   1074 
   1075 Row polymorphism, also known as "static duck typing" is a type system feature
   1076 which allows a single type variable to be used as a place holder for several
   1077 omitted fields, along with their types. The @racketmodname[phc-adt] library
   1078 supports a limited form of row polymorphism: for most operations, a set of
   1079 tuples of omitted field names must be specified, thereby indicating a bound on
   1080 the row type variable.
   1081 
   1082 This is both an limitation of our implementation (to reduce the combinatorial
   1083 explosion of possible input and output types), as well as a desirable feature.
   1084 Indeed, this library is intended to be used to write compilers, and a compiler
   1085 pass should have a precise knowledge of the intermediate representation it
   1086 manipulates. It is possible that a compiler pass may operate on several
   1087 similar intermediate representations (for example a full-blown representation
   1088 for actual compilation and a minimal representation for testing purposes),
   1089 which makes row polymorphism desirable. It is however risky to allow as an
   1090 input to a compiler pass any data structure containing at least the minimum
   1091 set of required fields: changes in the intermediate representation may add new
   1092 fields which should, semantically, be handled by the compiler pass. A
   1093 catch-all row type variable would simply ignore the extra fields, without
   1094 raising an error. Thanks to the bound which specifies the possible tuples of
   1095 omitted field names, changes to the the input type will raise a type error,
   1096 bringing the programmer's attention to the issue. If the new type is legit,
   1097 and does not warrant a modification of the pass, the fix is easy to implement:
   1098 simply adding a new tuple of possibly omitted fields to the bound (or
   1099 replacing an existing tuple) will allow the pass to work with the new type.
   1100 If, on the other hand, the pass needs to be modified, the type system will
   1101 have successfully caught a potential issue.
   1102 
   1103 This section presents the implementation of the features which allow a limited
   1104 form of row polymorphism, as well as structural subtyping.
   1105 
   1106 @subsection{Type for any tagged structure containing a given set of fields}
   1107 
   1108 @defproc[#:kind "for-syntax function"
   1109          (has-fields [stx-fields (syntax/c (listof identifier?))])
   1110          (listof (cons/c identifier?
   1111                          (cons/c identifier?
   1112                                  (listof identifier?))))]{
   1113                                     
   1114  Returns a list of tagged structures which have all of the given fields. Each
   1115  tagged structure list with the low-level struct's id as the first element, the
   1116  tag name as the second element, followed by the whole list of fields which
   1117  belong to that tagged structure.}
   1118 
   1119 @chunk[<has-fields>
   1120        (define-for-syntax has-fields
   1121          (λ/syntax-case (fieldᵢ …) ()
   1122            (map (λ (t+fields)
   1123                   (with-syntax ([(tag fieldᵢ …) t+fields])
   1124                     (list* (make-struct-identifier-common #t #'(fieldᵢ …))
   1125                            #'tag
   1126                            (sort-fields #'(fieldᵢ …)))))
   1127                 (filter (λ (s)
   1128                           (andmap (λ (f) (member f (cdr s)))
   1129                                   (syntax->datum #'(fieldᵢ …))))
   1130                         <all-remembered-tagged-structures>))))]
   1131 
   1132 @defform[#:kind "for-syntax function"
   1133          (has-fields/common #'(fieldᵢ …))]{
   1134  Returns a list of ``common'' structs which have all of the given fields. Each
   1135  ``common'' struct is represented as a pair of the struct's id and the whole
   1136  list of fields which belong to that tagged structure.}
   1137 
   1138 @chunk[<has-fields>
   1139        (define-for-syntax (has-fields/common stx-fields)
   1140          (remove-duplicates (map (λ (s) (cons (car s) (cddr s)))
   1141                                  (has-fields stx-fields))
   1142                             free-identifier=?
   1143                             #:key car))]
   1144 
   1145 @defform[#:kind "for-syntax function"
   1146          (has-fields/type #'([fieldᵢ τᵢ] …))]{                                  
   1147  Returns the syntax for the union type of several ``common'' structs. Each
   1148  tagged structure has all of the given fields, and each @racket[fieldᵢ] is of
   1149  the corresponding type @racket[τᵢ]. The other extra fields which are not part
   1150  of the @racket[#'([fieldᵢ τᵢ] …)] specification have the @racket[Any] type.}
   1151 
   1152 @chunk[<has-fields/type>
   1153        (define-for-syntax has-fields/type
   1154          (λ/syntax-case ([fieldᵢ τᵢ] …) ()
   1155            (define/with-syntax ((sⱼ all-fieldⱼₖ …) …)
   1156              (has-fields/common #'(fieldᵢ …)))
   1157            (define/with-syntax ((all-field-τⱼₖ …) …)
   1158              (template
   1159               ([(!cdr-assoc #:default Any all-fieldⱼₖ [fieldᵢ . τᵢ] …) …] …)))
   1160            #'(U (maybe-apply-type sⱼ all-field-τⱼₖ …) …)))]
   1161 
   1162 @subsection{Changing the tag of a tagged structure}
   1163 
   1164 @defform[(change-tag instance [(tagᵢ fieldᵢⱼ …) new-tagᵢ] …)]{ The
   1165  @racket[change-tag] macro takes an instance of a tagged structure, and
   1166  produces a new tagged structure instance with a different tag name. The
   1167  @racket[instance]'s type must be one of @racket[(tagged tagᵢ fieldᵢⱼ …) …].
   1168  The new instance will contain the same fields as the original, but its tag
   1169  name will be the @racket[new-tagᵢ] corresponding to the input's type.}
   1170 
   1171 @CHUNK[<change-tag>
   1172        (define-syntax/case (change-tag [(tagᵢ fieldᵢⱼ …) new-tagᵢ] …)
   1173          <change-tag-factored-out>
   1174          #`(cond #,(stx-map <change-tag-case>
   1175                             #'([tagᵢ (fieldᵢⱼ …) new-tagᵢ]))))]
   1176 
   1177 @chunk[<change-tag-factored-out>
   1178        (define old-s (check-remembered-tagged! #'(tag fieldⱼₛ)))]
   1179 
   1180 @chunk[<change-tag-case>
   1181        (λ/syntax-case (tag (fieldⱼ …) new-tag) ()
   1182          (define/with-syntax (fieldⱼₛ …) (sort-fields #'(fieldⱼ …)))
   1183          (define new-s (check-remembered-tagged! #'(new-tag fieldⱼₛ)))
   1184          #'[((struct-predicate old-s) instance)
   1185             ((struct-constructor new-s)
   1186              ((struct-accessor new-s fieldⱼₛ) instance) …)])]
   1187 
   1188 @subsection{Splitting a tagged structure}
   1189 
   1190 @defform[#:literals (: U)
   1191          (split instance : (U (tagᵢ fieldᵢⱼ …) …) requestedₖ …)]{
   1192  The @racket[split] macro splits a tagged structure into two tagged
   1193  structures. The first contains the @racket[requestedₖ …] fields, while the
   1194  second contains all other fields. The two new tagged structures have the same
   1195  tag as the original instance. This can however be altered later on using
   1196  @racket[change-tag].
   1197 
   1198  The expression generated by @racket[split] produces two values, one for each
   1199  new tagged structure.
   1200 
   1201  Since the type of the @racket[_instance] is not known at compile-time, this
   1202  form requires that the user specify a union of possible tagged structure
   1203  types. In theory, it would be possible to use the list of all tagged
   1204  structures, but this would result in a @racket[cond] testing over a large
   1205  number of impossible cases.
   1206 
   1207  The @racketmodname[trivial] library could help by tracking the type of
   1208  expressions in simple cases. That information could then be used to infer the
   1209  list of possible tagged structures. The explicit annotation would then become
   1210  mandatory only when the type could not be inferred.}
   1211 
   1212 @; TODO: should split be allowed for nodes ?
   1213 
   1214 The @racket[split] macro generates a @racket[cond] form, with one clause for
   1215 each possible instance type. In each @racket[cond] clause, the
   1216 @racket[requestedₖ …] and the other fields are separated into two different
   1217 tagged structures, the first .
   1218 
   1219 @CHUNK[<split>
   1220        (define-syntax split
   1221          (syntax-parser
   1222            #:literals (U)
   1223            [(_ instance :colon (U (~and τᵢ (tagᵢ fieldᵢⱼ …)) …) requestedₖ …)
   1224             <split-check>
   1225             <split-compute-extra-fields>
   1226             <split-case-factored-out>
   1227             #`(cond
   1228                 #,@(stx-map <split-case> #'([tagᵢ (extra-fieldᵢₗ …)] …)))]))]
   1229 
   1230 The @racket[split] macro first computes the set of
   1231 extra fields for each possible input type:
   1232 
   1233 @chunk[<split-compute-extra-fields>
   1234        (define/with-syntax ((extra-fieldᵢₗ …) …)
   1235          (stx-map (λ (x)
   1236                     (free-id-set->list
   1237                      (free-id-set-subtract x requested-id-set)))
   1238                   instance-id-sets))]
   1239 
   1240 It then generates a cond clause for each possible input type, which tests
   1241 whether the instance belongs to that type. If it is the case, then the body of
   1242 the clause
   1243 
   1244 @chunk[<split-case-factored-out>
   1245        (define/with-syntax (requestedₖₛ …) (sort-fields #'(requestedₖ …)))]
   1246 
   1247 @chunk[<split-case>
   1248        (λ/syntax-case (tag (extraₗ …)) ()
   1249          (define/with-syntax (extraₗₛ …) (sort-fields #'(extraₗ …)))
   1250          (define/with-syntax s-requested (check-remembered-tagged! #'(tag requestedₖ …)))
   1251          (define/with-syntax s (check-remembered-tagged! #'(tag requestedₖ … extraₗ …)))
   1252          (define/with-syntax c (check-remembered-common! #'(tag requestedₖ … extraₗ …)))
   1253          (define/with-syntax s-extra (check-remembered-tagged! #'(tag extraₗ …)))
   1254          (code:comment "the generated cond clause:")
   1255          #'[((struct-predicate s) instance)
   1256             (values ((struct-constructor s-requested)
   1257                      ((struct-accessor c requestedₖₛ) instance) …)
   1258                     ((struct-constructor s-extra)
   1259                      ((struct-accessor c extraₗₛ) instance) …))])]
   1260 
   1261 The argument-verification code for @racket[split] is given below. It uses
   1262 @racket[immutable-free-id-set]s to quickly compute the set of identifiers
   1263 present within @racket[requestedₖ …] but missing from one of the
   1264 @racket[fieldᵢⱼ …] tuples.
   1265 
   1266 @chunk[<split-check>
   1267        (define instance-id-sets
   1268          (stx-map (∘ immutable-free-id-set syntax->list) #'((fieldᵢⱼ …) …)))
   1269        
   1270        (define requested-id-set
   1271          (immutable-free-id-set (syntax->list #'(requestedₖ …))))
   1272        
   1273        (for ([τ (in-syntax #'(τᵢ …))]
   1274              [instance-id-set instance-id-sets])
   1275          (let ([missing (free-id-set-subtract requested-id-set
   1276                                               instance-id-set)])
   1277            (unless (free-id-set-empty? missing)
   1278              <split-error>)))]
   1279 
   1280 If there are such missing identifiers, the macro raises an error, otherwise
   1281 the computation proceeds normally:
   1282 
   1283 @chunk[<split-error>
   1284        (raise-syntax-error
   1285         'split
   1286         (format "The requested fields ~a are missing from the instance type ~a"
   1287                 (free-id-set->list missing)
   1288                 τ)
   1289         this-syntax
   1290         τ
   1291         (free-id-set->list missing))]
   1292 
   1293 @defform[(split/type #'((U (tagᵢ [fieldᵢⱼ τᵢⱼ] …) …) requestedₖ …))]{
   1294  We also define a @racket[split/type] for-syntax function, which returns the
   1295  syntax for the union type of the extra fields of a @racket[split] operation,
   1296  i.e. the type of the second value produced by @racket[split].}
   1297 
   1298 @CHUNK[<split>
   1299        (define-for-syntax split/type
   1300          (syntax-parser
   1301            #:literals (U)
   1302            [((U {~and τᵢ (tagᵢ [fieldᵢⱼ τᵢⱼ] …)} …) requestedₖ …)
   1303             <split-check>
   1304             (define/with-syntax (([extra-fieldᵢₗ . extra-τᵢₗ] …) …)
   1305               (for/list ([field+τⱼ… (in-syntax #'(([fieldᵢⱼ . τᵢⱼ] …) …))])
   1306                 (~for/list ([($stx [field . τ]) (in-syntax field+τⱼ…)]
   1307                             #:unless (free-id-set-member? requested-id-set
   1308                                                           #'field))
   1309                            #'[field . τ])))
   1310             #`(U #,@(stx-map tagged-type! #'([tagᵢ (extra-fieldᵢₗ …)] …)))]))]
   1311 
   1312 @subsection{Merging two tagged structures}
   1313 
   1314 @defform[#:literals (U :)
   1315          (merge instance-a instance-b
   1316                 : (U [(tag-aᵢ field-aᵢⱼ …) (tag-bₖ field-bₖₗ …)] …))]{
   1317  The @racket[merge] macro merges two tagged structures into a single one. The
   1318  resulting structure will contain all the fields
   1319  @racket[field1ᵢⱼ … field2ₖₗ …], and will have the same tag as
   1320  @racket[instance1] (although the tag can be changed later on using
   1321  @racket[change-tag]).
   1322 
   1323  Since the type of @racket[_instance1] and @racket[_instance2] is not known at
   1324  compile-time, this form requires that the user specify a union of possible
   1325  tagged structure types for both instances. In theory, it would be possible to
   1326  use the list of all tagged structures, but the resulting @racket[cond] would
   1327  test for each possible pair of tagged structure types. In other words, the
   1328  number of pairs of types to account for would be the Cartesian product of all
   1329  tagged structures used in the program. Clearly, this is not a viable solution.
   1330 
   1331  The @racketmodname[trivial] library could help by tracking the type of
   1332  expressions in simple cases. That information could then be used to infer the
   1333  list of possible tagged structures. The explicit annotation would then become
   1334  mandatory only when the type could not be inferred.
   1335 
   1336  If the @racketmodname[trivial] library were to be used, node types should be
   1337  excluded. Indeed, the node types rely on the fact that they cannot be
   1338  constructed outside of a graph to provide useful guarantees (e.g. the
   1339  possibility to map over all nodes of a given type contained within a graph).}
   1340 
   1341 @CHUNK[<merge>
   1342        (define-syntax merge
   1343          (syntax-parser
   1344            #:literals (U)
   1345            [(_ instance-a instance-b
   1346                :colon [U [(~and τ-a (tag-aᵢ field-aᵢⱼ …))
   1347                           (~and τ-b (tag-bₖ field-bₖₗ …))] …])
   1348             #`(cond
   1349                 #,@(stx-map <merge-case> #'([(τ-a tag-aᵢ field-aᵢⱼ …)
   1350                                              (τ-b tag-bₖ field-bₖₗ …)]
   1351                                             …)))]))]
   1352 
   1353 @; TODO: refactor to avoid the `and` within the cond clauses, as TR might not
   1354 @; handle it well. Instead, use nested conds, and group by (tag-aᵢ field-aᵢⱼ …)
   1355 
   1356 @CHUNK[<merge-case>
   1357        (λ/syntax-case [(τ-a tag-a field-aⱼ …) (τ-b tag-b field-bₗ …)] ()
   1358          <merge-check>
   1359          (define/with-syntax s-a (check-remembered-tagged! #'(tag-a field-aⱼ …)))
   1360          (define/with-syntax c-a (check-remembered-common! #'(tag-a field-aⱼ …)))
   1361          (define/with-syntax s-b (check-remembered-tagged! #'(tag-b field-bₗ …)))
   1362          (define/with-syntax c-b (check-remembered-common! #'(tag-b field-bₗ …)))
   1363          (define/with-syntax (field-aⱼₛ …) (sort-fields #'(field-aⱼ …)))
   1364          (define/with-syntax (field-bₗₛ …) (sort-fields #'(field-bₗ …)))
   1365          (define s-new (check-remembered-tagged!
   1366                         #'(tag-a field-aⱼₛ … field-bₗₛ …)))
   1367          #`[(and ((struct-predicate s-a) instance-a)
   1368                  ((struct-predicate s-b) instance-b))
   1369             (#,(tagged-infer-builder! #'(tag-a field-aⱼₛ … field-bₗₛ …))
   1370              (force ((struct-accessor c-a field-aⱼₛ) instance-a))
   1371   1372              (force ((struct-accessor c-b field-bₗₛ) instance-b))
   1373              …)])]
   1374 
   1375 @chunk[<merge-check>
   1376        (define fields-a-id-set
   1377          (immutable-free-id-set (syntax->list #'(field-aⱼ …))))
   1378        (define fields-b-id-set
   1379          (immutable-free-id-set (syntax->list #'(field-bₗ …))))
   1380        (let ([intersection (free-id-set-intersect fields-a-id-set
   1381                                                   fields-b-id-set)])
   1382          (unless (free-id-set-empty? intersection)
   1383            <merge-error>))]
   1384 
   1385 @chunk[<merge-error>
   1386        (raise-syntax-error
   1387         'merge
   1388         (format "The fields ~a are present in both tagged structures ~a and ~a"
   1389                 (free-id-set->list intersection)
   1390                 #'τ-a
   1391                 #'τ-b)
   1392         this-syntax
   1393         #'τ-a
   1394         (free-id-set->list intersection))]
   1395 
   1396 @defform[(merge/type #'(U [(tag-aᵢ [field-aᵢⱼ τ-aᵢⱼ] …)
   1397                            (tag-bᵢ [field-bᵢⱼ τ-bᵢⱼ] …)] …))]{
   1398  We also define a @racket[merge/type] for-syntax function, which returns the
   1399  syntax for the union type of the extra fields of a @racket[split] operation,
   1400  i.e. the type of the second value produced by @racket[split].}
   1401 
   1402 @CHUNK[<merge>
   1403        (define-for-syntax merge/type
   1404          (syntax-parser
   1405            #:literals (U)
   1406            [(U [(~and τ-a (tag-aᵢ field-aᵢⱼ …))
   1407                 (~and τ-b (tag-bₖ field-bₖₗ …))] …)
   1408             #`(U #,@(stx-map <merge-type-case>
   1409                              #'([tag-aᵢ field-aᵢⱼ … field-bₖₗ …] …)))]))]
   1410 
   1411 @CHUNK[<merge-type-case>
   1412        (λ/syntax-case [(τ-a tag-a field-aⱼ …) (τ-b tag-b field-bₗ …)] ()
   1413          <merge-check>
   1414          (tagged-type! #'[tag-a field-aⱼ … field-bₗ …]))]
   1415 
   1416 @subsection{Updating a tagged structure}
   1417 
   1418 @defform[#:literals (U :)
   1419          (with+ instance : (U (tagᵢ fieldᵢⱼ …) …)
   1420                [new-field value] …)]{
   1421  The @racket[with+] macro produces a tagged structure instance containing the
   1422  same fields as @racket[instance], extended with the given @racket[new-field]s.
   1423  None of the @racket[new-field …] must be present in the original
   1424  @racket[instance]. 
   1425 
   1426  Since the type of the @racket[_instance] is not known at compile-time, this
   1427  form requires that the user specify a union of possible tagged structure types
   1428  for the instance. In theory, it would be possible to use the list of all
   1429  tagged structures, but the resulting @racket[cond] would test for a large
   1430  number of impossible cases.
   1431 
   1432  The @racketmodname[trivial] library could help by tracking the type of
   1433  expressions in simple cases. That information could then be used to infer the
   1434  list of possible tagged structures. The explicit annotation would then become
   1435  mandatory only when the type could not be inferred.
   1436 
   1437  If the @racketmodname[trivial] library were to be used, node types should be
   1438  excluded. Indeed, the node types rely on the fact that they cannot be
   1439  constructed outside of a graph to provide useful guarantees (e.g. the
   1440  possibility to map over all nodes of a given type contained within a graph).
   1441  Instead, the normal tagged structure with the same name and fields can be
   1442  returned.}
   1443 
   1444 
   1445 @CHUNK[<with+>
   1446        (define-syntax/parse (with+ instance
   1447                                   :colon (U {~and τᵢ (tagᵢ fieldᵢⱼ …)} …)
   1448                                   [new-fieldₖ valueₖ] …)
   1449          <with+-check>
   1450          #'(with! instance : (U (tagᵢ fieldᵢⱼ …) …) [new-fieldₖ valueₖ] …))]
   1451 
   1452 @chunk[<with+-check>
   1453        (define instance-id-sets
   1454          (stx-map (∘ immutable-free-id-set syntax->list) #'([fieldᵢⱼ …] …)))
   1455        (define new-fields-id-set
   1456          (immutable-free-id-set (syntax->list #'(new-fieldₖ …))))
   1457        (for ([τ (in-syntax #'(τᵢ …))]
   1458              [instance-id-set instance-id-sets])
   1459          (let ([intersection (free-id-set-intersect new-fields-id-set
   1460                                                     instance-id-set)])
   1461            (unless (free-id-set-empty? intersection)
   1462              <with+-error>)))]
   1463 
   1464 @chunk[<with+-error>
   1465        (raise-syntax-error
   1466         'with+
   1467         (format "The new fields ~a are already present in the instance type ~a"
   1468                 (map syntax->datum (free-id-set->list intersection))
   1469                 (syntax->datum τ))
   1470         this-syntax
   1471         τ
   1472         (free-id-set->list intersection))]
   1473 
   1474 @defform[#:literals (U :)
   1475          (with! instance : (U (tagᵢ fieldᵢⱼ …) …)
   1476                 [updated-field value] …)]{
   1477  Like @racket[with+], but this version allows overwriting fields, i.e. the
   1478  @racket[updated-field]s may already be present in the @racket[instance].
   1479  Although the @racket[!] is traditionally used in Racket to indicate operations
   1480  which mutate data structures, in this case it merely indicates that the given
   1481  fields may exist in the original instance. Since a fresh updated copy of the
   1482  original instance is created, this operation is still pure.
   1483 
   1484  The same restrictions concerning nodes apply.}
   1485 
   1486 
   1487 @CHUNK[<with!>
   1488        (define-syntax with!
   1489          (syntax-parser
   1490            #:literals (U)
   1491            [(_ instance :colon (U (tagᵢ fieldᵢⱼ …) …) [updated-fieldₖ valueₖ] …)
   1492             #`(cond
   1493                 #,@(stx-map <with!-case> #'([tagᵢ fieldᵢⱼ …] …)))]))]
   1494 
   1495 @CHUNK[<with!-case>
   1496        (λ/syntax-case (tag fieldⱼ …) ()
   1497          (define/with-syntax old-s (check-remembered-tagged! #'(tag fieldⱼ …)))
   1498          (define/with-syntax old-c (check-remembered-common! #'(tag fieldⱼ …)))
   1499          (define field→value
   1500            (make-free-id-table
   1501             (stx-map syntax-e <with!-fieldⱼ-assoc>)))
   1502          <with!-fieldⱼ-overwritten>
   1503          (define/with-syntax ([fieldₗ . maybe-overwrittenₗ] …)
   1504            (free-id-table-map field→value cons))
   1505          #`[((struct-predicate old-s) instance)
   1506             (#,(tagged-infer-builder! #'(tag fieldₗ …)) maybe-overwrittenₗ …)])]
   1507 
   1508 The implementation works by initially mapping every @racket[fieldⱼ] identifier
   1509 to its value in the original instance:
   1510 
   1511 @chunk[<with!-fieldⱼ-assoc>
   1512        #'([fieldⱼ . (force ((struct-accessor old-c fieldⱼ) instance))] …)]
   1513 
   1514 The entries corresponding to an @racket[updated-fieldₖ] are then overwritten
   1515 in the table:
   1516 
   1517 @chunk[<with!-fieldⱼ-overwritten>
   1518        (for ([updated-field (in-syntax #'(updated-fieldₖ …))]
   1519              [value (in-syntax #'(valueₖ …))])
   1520          (free-id-table-set! field→value updated-field value))]
   1521 
   1522 @defform[#:literals (U :)
   1523          (with!! instance : (U (tagᵢ fieldᵢⱼ …) …)
   1524                  [updated-field value] …)]{
   1525  Like @racket[with!], but checks that all the given fields are already present
   1526  in the original instance. In other words, it does not change the type of the
   1527  instance, and merely performs a functional update of the given fields. This
   1528  version works on a much smaller set of types (namely those containing all the
   1529  given fields), so the annotation is optional.
   1530 
   1531  The same restrictions concerning nodes apply.}
   1532 
   1533 @; no update allowed for nodes
   1534 @; include only the types which have all of the given fields
   1535 @chunk[<with!!>
   1536        (define-syntax with!!
   1537          (syntax-parser
   1538            (code:comment "Auto-detect the set of tagged structures containing")
   1539            (code:comment "all the updated fields.")
   1540            [(_ instance
   1541                [updated-fieldₖ valueₖ] …)
   1542             #:with ([sᵢ tagᵢ fieldᵢⱼ …] …) (has-fields #'(updated-fieldₖ …))
   1543             #'(with! instance : (U (tagᵢ fieldᵢⱼ …) …)
   1544                      [updated-fieldₖ valueₖ] …)]
   1545            (code:comment "Use an explicit list of tagged structures containing")
   1546            (code:comment "all the updated fields.")
   1547            [(_ instance :colon (U {~and τᵢ (tagᵢ fieldᵢⱼ …)} …)
   1548                [updated-fieldₖ valueₖ] …)
   1549             <with!!-check>
   1550             #'(with! instance : (U (tagᵢ fieldᵢⱼ …) …)
   1551                      [updated-fieldₖ valueₖ] …)]))]
   1552 
   1553 @chunk[<with!!-check>
   1554        (define instance-id-sets
   1555          (stx-map (∘ immutable-free-id-set syntax->list) #'([fieldᵢⱼ …] …)))
   1556        (define updated-id-set
   1557          (immutable-free-id-set (syntax->list #'(updated-fieldₖ …))))
   1558        (for ([instance-id-set instance-id-sets]
   1559              [τ (in-syntax #'(τᵢ …))])
   1560          (let ([missing (free-id-set-subtract updated-id-set
   1561                                               instance-id-set)])
   1562            (unless (free-id-set-empty? missing)
   1563              <with!!-error>)))]
   1564 
   1565 @chunk[<with!!-error>
   1566        (raise-syntax-error
   1567         'with!!
   1568         (format "The updated fields ~a are not present in the instance type ~a"
   1569                 (map syntax->datum (free-id-set->list missing))
   1570                 (syntax->datum τ))
   1571         this-syntax
   1572         τ
   1573         (free-id-set->list missing))]
   1574 
   1575 @defproc[#:kind "for-syntax function"
   1576          (tagged-struct-id? [id any/c])
   1577          (or/c #f
   1578                (cons/c (or/c 'tagged 'node)
   1579                        (cons/c identifier
   1580                                (listof identifier))))]{
   1581  The @racket[tagged-struct-id?] expects an identifier. When the @racket[id] is
   1582  an identifier which refers to a @racket[struct] definition corresponding to a
   1583  tagged structure, @racket[tagged-struct-id?] returns a list containing the
   1584  tagged structure's tag name and fields, prefixed with either @racket['tagged]
   1585  or @racket['node], depending on whether the given struct id corresponds to a
   1586  tagged structure's struct, or to a node's struct. Otherwise,
   1587  @racket[tagged-struct-id?] returns @racket[#false].
   1588  
   1589  This can be used to recognise occurrences of tagged structures within
   1590  fully-expanded types.}
   1591 
   1592 @CHUNK[<tagged-struct-id?>
   1593        (define-for-syntax tagged-struct-ids-cache #f)
   1594        (define-for-syntax (tagged-struct-id? id)
   1595          <tagged-struct-ids-init-cache>
   1596          (and (identifier? id)
   1597               (free-id-table-ref tagged-struct-ids-cache id #f)))]
   1598 
   1599 The @racket[tagged-struct-id] function uses a free-identifier table which
   1600 associates struct identifiers to their corresponding tag name and fields
   1601 (prefixed with @racket['tagged] or @racket['node]). The table is initialised
   1602 when @racket[tagged-struct-id?] is called for the first time. It could not be
   1603 initialised beforehand, as the @racket[adt-init] macro needs to be called by the
   1604 user code first.
   1605 
   1606 @chunk[<tagged-struct-ids-init-cache>
   1607        (unless tagged-struct-ids-cache
   1608          (set! tagged-struct-ids-cache
   1609                (make-immutable-free-id-table
   1610                 (append-map (λ (s)
   1611                               (list (list* (make-struct-identifier-tagged #t s)
   1612                                            'tagged
   1613                                            s)
   1614                                     (list* (make-struct-identifier-node #t s)
   1615                                            'node
   1616                                            s)))
   1617                             <all-remembered-tagged-structures>))))]
   1618 
   1619 @section{Putting it all together}
   1620 
   1621 The low-level implementation of algebraic data types is split into three
   1622 modules: @tc[sorting-and-identifiers], @tc[pre-declare] and the main module.
   1623 Furthermore, the section
   1624 @secref["node-low-level" #:tag-prefixes '("phc-adt/node-low-level")],
   1625 implemented as a separate file, contains the implementation details for printing
   1626 and comparing nodes.
   1627 
   1628 @chunk[<*>
   1629        <module-sorting-and-identifiers>
   1630        <module-pre-declare>
   1631        <main-module>]
   1632 
   1633 The @tc[sorting-and-identifiers] module contains the utility functions related
   1634 to sorting fields (to obtain a canonical representation of the tagged structure
   1635 descriptor), and the functions which derive the @tc[struct] identifiers for
   1636 tagged structures, nodes and the ``common'' supertype of all tagged structures
   1637 which share the same set of fields. These @tc[struct] identifiers are derived
   1638 from the list of field names and the tag name.
   1639 
   1640 @chunk[<module-sorting-and-identifiers>
   1641        (module sorting-and-identifiers racket/base
   1642          (require racket/list
   1643                   racket/format
   1644                   racket/contract
   1645                   phc-toolkit/untyped
   1646                   (for-template "ctx.hl.rkt"))
   1647 
   1648          (provide make-struct-identifier-common
   1649                   make-struct-identifier-tagged
   1650                   make-struct-identifier-node
   1651                   make-struct-identifier-tagged-pred
   1652                   sort-fields
   1653                   sort-fields-alist)
   1654 
   1655          <sort-fields>
   1656          <sort-fields-alist>
   1657          <make-struct-identifier-from-list>
   1658          <make-struct-identifier-common>
   1659          <make-struct-identifier-tagged>
   1660          <make-struct-identifier-node>
   1661          <make-struct-identifier-tagged-pred>)]
   1662 
   1663 The @tc[pre-declare] submodule contains everything which concerns the
   1664 pre-declaration of structs. It also uses the printer and comparer for nodes from
   1665 @secref["node-low-level" #:tag-prefixes '("phc-adt/node-low-level")].
   1666 
   1667 @CHUNK[<module-pre-declare>
   1668        (module pre-declare typed/racket/base
   1669          (require racket/promise
   1670                   racket/string
   1671                   racket/require
   1672                   phc-toolkit
   1673                   remember
   1674                   typed-struct-props
   1675                   "node-low-level.hl.rkt"
   1676                   "ctx.hl.rkt"
   1677                   (only-in type-expander unsafe-cast/no-expand)
   1678                   (for-syntax racket/base
   1679                               racket/syntax
   1680                               racket/list
   1681                               racket/set
   1682                               racket/function
   1683                               (subtract-in syntax/stx phc-toolkit/untyped)
   1684                               syntax/parse
   1685                               syntax/parse/experimental/template
   1686                               syntax/strip-context
   1687                               phc-toolkit/untyped))
   1688          (require (for-syntax (submod ".." sorting-and-identifiers)))
   1689 
   1690          (provide (struct-out TaggedTop-struct)
   1691                   pre-declare-all-tagged-structure-structs
   1692                   pre-declare-group)
   1693 
   1694          (begin-for-syntax
   1695            (define-template-metafunction !maybe-apply
   1696              (λ (stx)
   1697                (syntax-case stx ()
   1698                  [(_ t) #'t]
   1699                  [(_ t . args) #'(t . args)]))))
   1700          
   1701          <remember-empty-tagged-structure>
   1702          <remember-one-constructor>
   1703          <TaggedTop>
   1704          <custom-write>
   1705          <equal+hash>
   1706          <pre-declare-all-tagged-structure-structs>)]
   1707 
   1708 The main module contains all the code related to remembering the tagged
   1709 structures across compilations. It also contains many for-syntax functions
   1710 which, given the tag name and fields of a tagged structure, produce syntax for
   1711 that tagged structure's builder function, type, predicate and match pattern.
   1712 
   1713 @chunk[<main-module>
   1714        (require phc-toolkit
   1715                 remember
   1716                 racket/promise
   1717                 (submod "." pre-declare)
   1718                 type-expander
   1719                 "ctx.hl.rkt"
   1720                 (for-syntax racket/base
   1721                             racket/syntax
   1722                             racket/list
   1723                             racket/set
   1724                             racket/function
   1725                             phc-toolkit/untyped
   1726                             syntax/parse
   1727                             syntax/parse/experimental/template
   1728                             syntax/id-set
   1729                             syntax/id-table
   1730                             generic-bind
   1731                             (submod "." sorting-and-identifiers)))
   1732 
   1733        (provide (for-syntax tagged-builder!
   1734                             tagged-∀-builder!
   1735                             tagged-infer-builder!
   1736                             tagged-type!
   1737                             tagged-∀-type!
   1738                             tagged-infer-type!
   1739                             tagged-predicate!
   1740                             tagged-pred-predicate!
   1741                             tagged-any-predicate!
   1742                             tagged-match!
   1743                             tagged-anytag-match!
   1744                             check-remembered-common!
   1745                             check-remembered-tagged!
   1746                             check-remembered-node!
   1747                             check-remembered-?!
   1748                             has-fields
   1749                             has-fields/common
   1750                             has-fields/type
   1751                             tagged-any-fields-type
   1752                             tagged-any-fields-predicate
   1753                             split/type
   1754                             merge/type
   1755                             tagged-struct-id?)
   1756                 tagged-get-field
   1757                 λ-tagged-get-field
   1758                 split
   1759                 merge
   1760                 with+
   1761                 with!
   1762                 with!!)
   1763 
   1764        (provide (all-from-out (submod "." pre-declare)))
   1765 
   1766        <check-remembered!>
   1767        <tagged-builder!>
   1768        <tagged-∀-builder!>
   1769        <tagged-infer-builder!>
   1770        <tagged-any-fields>
   1771        <tagged-type!>
   1772        <tagged-∀-type!>
   1773        <tagged-infer-type!>
   1774        <tagged-any-fields-type>
   1775        <tagged-predicate!>
   1776        <tagged-pred-predicate!>
   1777        <tagged-any-predicate!>
   1778        <tagged-any-fields-predicate>
   1779        <tagged-match!>
   1780        <tagged-anytag-match!>
   1781        <has-fields>
   1782        <has-fields/type>
   1783        <tagged-get-field>
   1784        <λ-tagged-get-field>
   1785        <split>
   1786        <merge>
   1787        <with+>
   1788        <with!>
   1789        <with!!>
   1790        <tagged-struct-id?>]