On this page:
8.1 Introduction
8.2 The polyvalent identifier constructor: type, match, builder and instance
«constructor»
«constructor?»
8.3 Type-expander
«name-id-mixin»
«∀-mixin»
«constructor-type-types-mixin»
«name-after-field-error»
«constructor-type-args-mixin»
«type-expander»
8.4 Match-expander
«match-expander»
8.5 Predicate
«predicate»
8.6 Instance creation
«infer-pat»
«call-expander-cases»1
«colon-pat»
«call-expander-cases»2
«!-pat»
«call-expander-cases»3
«dcolon-pat»
«call-expander-cases»4
«call-expander-cases»5
«value-maybe-type»
«literal-value»
«replace-chars»
«call-expander-rest»
«call-expander-rest-keyword»
«call-expander-empty-rest»
«call-expander-dotted-rest»
«type-label-syntax-class»
«call-expander»
8.7 Defining shorthands for constructors with define-constructor
«tag-kw-mixin»
«default-tag-name»
«predicate?-mixin»
«default-name?»
(colon-pat)
(!-pat)
(dcolon-pat)
«define-constructor»
«multi-id/define»
«type-expander/define»
«call-expander/define»
«normalize-type/define»1
«with-normalize»
«normalize-type/define»2
«match-expander/define»
«match-rest-signature/define»
«match-xlist/define»
«predicate/define»
8.8 Miscellanea
«constructor-values»
«ConstructorTop?»
«ConstructorTop»
8.9 Putting it all together
«*»
7.1

8 User API for constructors

    8.1 Introduction

    8.2 The polyvalent identifier constructor: type, match, builder and instance

    8.3 Type-expander

    8.4 Match-expander

    8.5 Predicate

    8.6 Instance creation

    8.7 Defining shorthands for constructors with define-constructor

    8.8 Miscellanea

    8.9 Putting it all together

8.1 Introduction

This file defines constructor, a form which allows tagging values, so that two otherwise identical values can be distinguished by the constructors used to wrap them. Coupled with the variants defined by this library, it implements a slight variation on the constructors and variants commonly found in other languages. The constructor form is effectively a wrapper around tagged structures, which stores all values within a single field named values.

The constructors defined in this library are "interned", meaning that two constructors in different files will be the same if they use same tag name. In other words, the tag of a constructor works in the same way as a symbol in Racket: unless otherwise specified, the same string of characters will always produce the same symbol, even across modules. The same goes for constructors: the same constructor name will always refer to the same type.

8.2 The polyvalent identifier constructor: type, match, builder and instance

We define the constructor macro which acts as a type, a match expander, and a constructor function (which can be called to create a tagged value, i.e. a constructor instance). It can also be directly given a value to directly produce a tagged value, i.e. a constructor instance.

The constructor? macro returns a predicate for the given constructor name, or checks if a value is an instance of the given constructor name. This form is implemented in «predicate» below.

8.3 Type-expander

The type-expander for constructor expects:

The three elements can appear in any order, with one constraint: the name must appear before the first type. Not only does it make more sense semantically, but it also avoids ambiguities when some of the types are plain type identifiers.

"The name must appear before any value or type"

(define-eh-alternative-mixin constructor-type-seq-args-mixin
  #:define-syntax-class constructor-type-seq-args-syntax-class
  (pattern {~mixin name-id-mixin})
  (pattern {~mixin types-mixin})
  (pattern {~mixin ∀-mixin}))

The type expander handles two cases: when type variables are present, it uses the low-level function tagged-∀-type!, otherwise it uses the low-level function tagged-type!. The constructor contains a (possibly improper) list of values. The type of that list is expressed using the syntax of the xlist library.

(λ/syntax-parse :constructor-type-seq-args-syntax-class
  (if (attribute tvars?)
      (tagged-∀-type! #'((tvarᵢ ) name [values (xlist τᵢ  . τ-rest)]))
      (tagged-type! #'(name [values (xlist τᵢ  . τ-rest)]))))

8.4 Match-expander

(syntax-parser
  [(name:id . pats)
   (tagged-match! #'(name [values (xlist . pats)]))])

The match expander simply matches the given patterns against the constructor’s single field, values. The patterns will usually match one value each, but the xlist pattern expander allows a more flexible syntax than the regular list match pattern.

8.5 Predicate

The constructor? macro expands to a predicate and accepts the same syntax as for the type expander, without polymorphic variables. Additionally the resulting type as expanded by xlist must be a suitable argument to make-predicate.

(λ/syntax-parse (name:id . types)
  (tagged-predicate! #'(name [values (xList . types)])))

8.6 Instance creation

The constructor macro can return a builder function or an instance. It accepts the following syntaxes:

All four forms accept a #:∀ (tvarᵢ ) specification, and the fourth injects a tvarᵢ type variable for values for which no type is given.

8.7 Defining shorthands for constructors with define-constructor

The define-constructor macro binds an identifier to a type-expander, match-expander and call-expander for the constructor with the same name. It also defines a predicate for that constructor type.

Like define-tagged, the constructor macro expects:

Unlike define-tagged, which also expects a list of field names possibly annotated with a type, the constructor macro instead expects a description of the list of values it contains. Three syntaxes are accepted:

These syntaxes control how the call expander for the defined name works, and have the same meaning as in the call expander for constructor (xlist, cast and single-argument xlist).

(define-syntax define-constructor
  (syntax-parser-with-arrows
   [(_ . (~no-order {~mixin name-id-mixin}
                    {~mixin ∀-mixin}
                    {~mixin tag-kw-mixin}
                    {~mixin predicate?-mixin}
                    (~once
                     (~and (~seq type-decls )
                           (~either «colon-pat»
                                    «!-pat»
                                    «dcolon-pat»)))))
    #:with tvarᵢ→Any (stx-map (const #'Any) #'(tvarᵢ ))
    «normalize-type/define»
    (quasisyntax/top-loc stx
      (begin
        «multi-id/define»
        «predicate/define»))]))

(define-multi-id name
  #:type-expander  (make-id+call-transformer «type-expander/define»)
  #:match-expander (make-rest-transformer    «match-expander/define»)
  #:else                                     «call-expander/define»)

#'(constructor tag-name
               #,@(when-attr tvars? #'(#:∀ (tvarᵢ )))
               τᵢ  . τ-rest)

#'(constructor tag-name
               #,@(when-attr tvars? #'(#:∀ (tvarᵢ )))
               type-decls )

In order to attach patterns to the xlist type, pre-process the types using normalize-xlist-type.

#:with «with-normalize» (normalize-xlist-type #'(τᵢ  . τ-rest) stx)

Once normalized, the types for the xlist are all of the form τᵢ ^ {repeat }, except for the rest type, which is always present including when it is Null, and is specified using #:rest rest-type.

({~seq normalized-τᵢ {~literal ^} (normalized-repeat )} 
 #:rest normalized-rest)

We then define an argument for the pattern expander corresponding to each type within the normalized sequence:

(define-temp-ids "~a/pat" (normalized-τᵢ ))

The match expander expects these patterns and a rest pattern:

(syntax-parser
  [({~var normalized-τᵢ/pat}  . {~either «match-rest-signature/define»})
   #'#,(tagged-match! #'(name [values «match-xlist/define»]))])

The rest pattern can be specified either using a dotted notation if it is a single term, using #:rest pat-rest, or can be omitted in which case it defaults to matching null. The following syntaxes are therefore accepted:

(#:rest pat-rest)
(~and () {~bind [pat-rest #'(? null?)]})
pat-rest:not-stx-pair

The match expander produces an xlist pattern using the given patterns and the optional rest pattern. The given patterns are repeated as within the type specification.

(and (? (make-predicate (xlist τᵢ  . τ-rest)))
     (split-xlist (list normalized-τᵢ/pat  pat-rest)
                  τᵢ  . τ-rest))

(define name?
  #,(if (attribute tvars?)
        (tagged-predicate!
         #'(tag-name [values ((xlist τᵢ  . τ-rest) tvarᵢ→Any)]))
        (tagged-predicate!
         #'(tag-name [values (xlist τᵢ  . τ-rest)]))))

8.8 Miscellanea

(define-syntax ConstructorTop?
  (make-id+call-transformer-delayed
   (λ ()
     #`(struct-predicate
        #,(check-remembered-common!
           #'(always-remembered values))))))

(define-type-expander (ConstructorTop stx)
  (syntax-case stx ()
    [id
     (identifier? #'id)
     #'((check-remembered-common!
         #'(always-remembered values))
        Any)]))

8.9 Putting it all together

«*» ::=
(require phc-toolkit
         "tagged.hl.rkt"
         "tagged-structure-low-level.hl.rkt"
         (only-in match-string [append match-append])
         type-expander
         xlist
         multi-id
         (for-syntax racket/base
                     syntax/parse
                     syntax/parse/experimental/template
                     racket/contract
                     racket/syntax
                     racket/string
                     racket/function
                     racket/list
                     type-expander/expander
                     phc-toolkit/untyped
                     extensible-parser-specifications))
 
(provide constructor-values
         constructor
         constructor?
         ConstructorTop
         ConstructorTop?
         define-constructor
         (for-syntax constructor-type-seq-args-syntax-class))
 
(begin-for-syntax
  (define-syntax-class not-stx-pair
    (pattern {~not (_ . _)}))
  «type-label-syntax-class»
  «name-id-mixin»
  «∀-mixin»
  «constructor-type-types-mixin»
  «constructor-type-args-mixin»
  «tag-kw-mixin»
  «predicate?-mixin»
  «replace-chars»
  «literal-value»
  «value-maybe-type»)
 
«constructor»
«constructor?»
«ConstructorTop»
«ConstructorTop?»
«define-constructor»
«constructor-values»