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?>]