On this page:
2.1 Example type expanders:   quasiquote and quasisyntax
«quotes»1
«quotes»2
«quotes»3
«quotes»4
«expand-quasiquote»
2.2 Implementation of the Let* special type expander form
«Let*»
2.3 curry
«curry»
2.4 Putting it all together
«*»
8.13.0.9

2 Some example type expanders🔗ℹ

2.1 Example type expanders: quasiquote and quasisyntax🔗ℹ

We define type expanders for quote, quasiquote, syntax and quasisyntax:

The next four special forms are implemented as type expanders with patch-type-expander because redefining their name (quote, quasiquote, syntax and quasisyntax) would conflict with existing identifiers. patch-type-expander uses a global persistant (across modules) for-syntax mutable table, which associates identifiers to type-expanders. typed/racket works in that way by associating data (their type) to existing identifiers. The mutable-match-lambda library on the other hand allows adding behaviour to an identifier after it is defined, but relies on some level of cooperation from that identifier, which may be less practical for built-in identifiers like quote. Relying on an external data structure to associate information with identifiers makes it possible to overload the meaning of quote or curry when used as a type expander, without having to alter their original definition. Another option would be to provide overloaded versions of these identifiers, to shadow those imported by the #lang module. This would however cause conflicts for curry when racket/function is explicitly required (instead of being required implicitly by #<procedure:hash-lang> racket, for example.

(patch-type-expander quote
  (λ (stx)
    (syntax-case stx ()
      [(_ T)
       (expand-quasiquote 'quote 1 #'T)])))

(patch-type-expander quasiquote
  (λ (stx)
    (syntax-case stx ()
      [(_ T)
       (expand-quasiquote 'quasiquote 1 #'T)])))

(patch-type-expander syntax
  (λ (stx)
    (syntax-case stx ()
      [(_ T)
       (expand-quasiquote 'syntax 1 #'T)])))

(patch-type-expander quasisyntax
  (λ (stx)
    (syntax-case stx ()
      [(_ T)
       (expand-quasiquote 'quasisyntax 1 #'T)])))

Their implementation is factored out into the expand-quasiquote for-syntax function. It is a reasonably complex showcase of this library’s functionality. typed/racket allows the use of quote to describe a type which contains a single inhabitant, the quoted datum. For example, (define-type foo '(a b (1 2 3) c)) declares a type foo which is equivalent to (List 'a 'b (List 1 2 3) 'c).

We build upon that idea to allow the use of syntax, quasiquote and quasisyntax. Both syntax and quasisyntax wrap each s-expression within the quoted datum with Syntaxof, which avoids the otherwise tedious declaration of the type for a piece of syntax. Both quasiquote and quasisyntax allow escaping the quoted datum (using unquote and unsyntax, respectively). A later version of this library could support unquote-splicing and unsyntax-splicing.

Using this type-expander, one can write

(define-type bar `(a ,Symbol (1 ,(U Number String) 3) c))

The above declaration gets expanded to:

(define-type bar (List 'a Symbol (List 1 (U Number String) 3) 'c))

The implementation of expand-quasiquote recursively traverses the type expression. The mode argument can be one of 'quote, 'quasiquote, 'syntax or 'quasisyntax. It is used to determine whether to wrap parts of the type with Syntaxof or not, and to know which identifier escapes the quoting (unquote or unsyntax). The depth argument keeps track of the quoting depth: in Racket `(foo `(bar ,baz)) is equivalent to (list 'foo (list 'quasiquote (list 'bar (list 'unquote 'baz)))) (two levels of unquote are required to escape the two levels of quasiquote), so we want the type to be (List 'foo (List 'quasiquote (List 'bar (List 'unquote 'baz)))).

(define (list*->list l)
  (if (pair? l)
      (cons (car l) (list*->list (cdr l)))
      (list l)))
(define (expand-quasiquote mode depth stx)
  (define (wrap t)
    (if (or (eq? mode 'syntax) (eq? mode 'quasisyntax))
        #`(Syntaxof #,t)
        t))
  (define (wrap-quote t)
    (if (or (eq? mode 'syntax) (eq? mode 'quasisyntax))
        #`(Syntaxof (No-Expand '#,t))
        #`(No-Expand '#,t)))
  (define expand-quasiquote-rec (curry expand-quasiquote mode depth))
  (syntax-parse stx
    [((~literal quote) T)
     (wrap #`(List #,(wrap-quote #'quote)
                   #,(expand-quasiquote-rec #'T)))]
    [((~literal quasiquote) T)
     (wrap #`(List #,(wrap-quote #'quasiquote)
                   #,(if (eq? mode 'quasiquote)
                         (expand-quasiquote mode (+ depth 1) #'T)
                         (expand-quasiquote-rec #'T))))]
    [((~literal unquote) T)
     (if (eq? mode 'quasiquote)
         (if (= depth 1)
             (expand-type #'T) ;; TODO: applicable? !!!!!!!!!!!!!!!!!!!!!!!!!!!!
             (wrap #`(List #,(wrap-quote #'unquote)
                           #,(expand-quasiquote mode (- depth 1) #'T))))
         (wrap #`(List #,(wrap-quote #'unquote)
                       #,(expand-quasiquote-rec #'T))))]
    [((~literal syntax) T)
     (wrap #`(List #,(wrap-quote #'quote)
                   #,(expand-quasiquote-rec #'T)))]
    [((~literal quasisyntax) T)
     (wrap #`(List #,(wrap-quote #'quasisyntax)
                   #,(if (eq? mode 'quasisyntax)
                         (expand-quasiquote mode (+ depth 1) #'T)
                         (expand-quasiquote-rec #'T))))]
    [((~literal unsyntax) T)
     (if (eq? mode 'quasisyntax)
         (if (= depth 1)
             (expand-type #'T) ;; TODO: applicable? !!!!!!!!!!!!!!!!!!!!!!!!!!!!
             (wrap #`(List #,(wrap-quote #'unsyntax)
                           #,(expand-quasiquote mode (- depth 1) #'T))))
         (wrap #`(List #,(wrap-quote #'unsyntax)
                       #,(expand-quasiquote-rec #'T))))]
    ;; TODO For lists, we should consider the cases where syntax-e gives
    ;; a pair vs the cases where it gives a list.
    [(T . U)
     #:when (syntax? (cdr (syntax-e stx)))
     (wrap #`(Pairof #,(expand-quasiquote-rec #'T)
                     #,(expand-quasiquote-rec #'U)))]
    [() (wrap #'Null)]
    [(T ...)
     #:when (list? (syntax-e stx))
     (wrap #`(List #,@(stx-map expand-quasiquote-rec #'(T ...))))]
    [whole
     #:when (pair? (syntax-e #'whole))
     #:with (T ... S) (list*->list (syntax-e #'whole))
     (wrap #`(List* #,@(stx-map expand-quasiquote-rec #'(T ... S))))]
    [#(T ...)
     (wrap #`(Vector #,@(stx-map expand-quasiquote-rec #'(T ...))))]
    [#&T (wrap #`(Boxof #,(expand-quasiquote-rec #'T)))]
    ; TODO: Prefab with #s(prefab-struct-key type ...)
    [T:id (wrap #'(No-Expand 'T))]
    [T #:when (string? (syntax-e #'T)) (wrap #'T)]
    [T:number (wrap #'T)]
    [T:keyword (wrap #'(No-Expand 'T))]
    [T:char (wrap #'T)]
    [#t (wrap #'True)]
    [#t (wrap #'False)]
    [_ (raise-syntax-error 'expand-quasiquoste
                           (format "Unknown quasiquote contents: ~a" stx)
                           stx)]))

2.2 Implementation of the Let* special type expander form🔗ℹ

The Let* special form is implemented in terms of Let, binding each variable in turn:

(define-type-expander (Let* stx)
  (syntax-case stx ()
    [(me ([var val] . rest) τ)
     (with-syntax ([L (datum->syntax #'here 'Let #'me #'me)]
                   [L* (datum->syntax #'here 'Let* #'me #'me)])
       #'(L ([var val])
            (L* rest
                τ)))]
    [(_ () τ) #'τ]))

2.3 curry🔗ℹ

The curry special form takes a type expander (or a polymorphic type) and some arguments. The whole form should appear in the first position of its containing form, which contains more arguments, or be bound with a Let or Letrec. curry appends the arguments in the outer form to the whole inner form, and expands the result. This really should be implemented as a type expander so that the partially-applied expander or polymorphic type can be bound using Let, for example, but for now it is hardcoded here.

(patch-type-expander curry
  (λ (stx)
    (syntax-case stx ()
      [(_ T Arg1 ...)
       #'(Λ (_ . Args2) #'(T Arg1 ... . Args2))])))

2.4 Putting it all together🔗ℹ

«*» ::=
(require "type-expander.hl.rkt"
         "identifiers.rkt"
         racket/function
         (for-syntax racket/base
                     (only-in racket/base [... ])
                     (submod "type-expander.hl.rkt" expander)
                     syntax/parse
                     syntax/stx
                     racket/function
                     racket/match))
(provide Let*)
 
«Let*»
 
(begin-for-syntax «expand-quasiquote»)
«quotes»
 
«curry»