www

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

guide.scrbl (19778B)


      1 #lang scribble/manual
      2 
      3 @(require scribble/example racket/sandbox
      4           (for-label racket/base
      5                      (except-in turnstile/turnstile ⊢ mk-~ mk-?))
      6           "doc-utils.rkt" "common.rkt")
      7 
      8 @title{The Turnstile Guide}
      9 
     10 This guide introduces Turnstile with the implementation of a simply-typed core
     11 language. It then reuses the simply-typed language implementation to implement
     12 a language with subtyping.
     13 
     14 @section[#:tag "judgements"]{A New Type Judgement}
     15 
     16 Turnstile's syntax borrows from that of conventional type
     17 judgements. Specifically, programmers may implement typed languages using a
     18 declarative syntax that interleaves program rewriting (i.e., macro expansion)
     19 and type checking. These new rules rely on two core
     20 @cite{bidirectional} judgements:
     21 @itemlist[#:style 'ordered
     22           @item{@verbatim|{Γ ⊢ e ≫ e- ⇒ τ}|
     23            reads: "In context Γ, @racket[e] expands to @racket[e-] and has type
     24            τ."}
     25           @item{@verbatim|{Γ ⊢ e ≫ e- ⇐ τ}|
     26            reads: "In context Γ, @racket[e] expands to @racket[e-] and must
     27            have type τ."
     28 
     29            The key difference is that τ is an output in the first relation and
     30            an input in the second relation.
     31 
     32            As will be shown below, these input and output positions
     33            conveniently correspond to @tech:stx-pats and @tech:stx-templates,
     34            respectively.}]
     35 
     36 For example, here are some rules that check and rewrite simply-typed
     37 lambda-calculus terms to the untyped lambda-calculus.
     38 
     39 @verbatim|{
     40       [x ≫ x- : τ] ∈ Γ
     41 [VAR] -----------------
     42       Γ ⊢ x ≫ x- ⇒ τ
     43            
     44       Γ,[x ≫ x- : τ1] ⊢ e ≫ e- ⇒ τ2
     45 [LAM] -------------------------------
     46       Γ ⊢ λx:τ1.e ≫ λx-.e- ⇒ τ1 → τ2
     47 
     48       Γ ⊢ e1 ≫ e1- ⇒ τ1 → τ2
     49       Γ ⊢ e2 ≫ e2- ⇐ τ1
     50 [APP] -------------------------
     51       Γ ⊢ e1 e2 ≫ e1- e2- ⇒ τ2
     52 }|
     53 
     54 @; Sec: define-typed-syntax ---------------------------------------------------
     55 @section[#:tag "define-typed-syntax"]{@racket[define-typed-syntax]}
     56 
     57 Here are implementations of the above rules using Turnstile 
     58 (we extended the forms to define multi-arity functions):
     59 
     60 @label-code["Initial function and application definitions"
     61 @#reader scribble/comment-reader
     62 (racketblock0
     63 ;; [LAM]
     64 (define-typed-syntax (λ ([x:id : τ_in:type] ...) e) ≫
     65   [[x ≫ x- : τ_in.norm] ... ⊢ e ≫ e- ⇒ τ_out]
     66   -------
     67   [⊢ (λ- (x- ...) e-) ⇒ (→ τ_in.norm ... τ_out)])
     68 
     69 ;; [APP]
     70 (define-typed-syntax (#%app e_fn e_arg ...) ≫
     71   [⊢ e_fn ≫ e_fn- ⇒ (~→ τ_in ... τ_out)]
     72   [⊢ e_arg ≫ e_arg- ⇐ τ_in] ...
     73   --------
     74   [⊢ (#%app- e_fn- e_arg- ...) ⇒ τ_out])
     75 )]
     76 
     77 The above code assumes some existing bindings:
     78 @itemlist[
     79  @item{@racket[→], a programmer-defined (or imported) type constructor, 
     80   see @secref{tycon};}
     81  @item{@racket[~→], a @tech:pat-expander associated with the @racket[→] type
     82   constructor;}
     83  @item{@racket[type], a @tech:stx-class for recognizing valid types that is 
     84   pre-defined by Turnstile;}
     85  @item{and core Racket forms suffixed with @litchar{-}, for example
     86   @racket[λ-], that are also predefined by Turnstile.}
     87  ]
     88 
     89 The @racket[define-typed-syntax] form resembles a conventional Racket macro
     90 definition: the above rules begin with an input pattern, where the leftmost
     91 identifier is the name of the macro, which is followed by a series of premises
     92 that specify side conditions and bind local pattern variables, and concludes
     93 with an output @|tech:stx-template|.
     94 
     95 More specifically, @racket[define-typed-syntax] is roughly an extension of 
     96 @racket[define-syntax-parser] in that:
     97 @itemlist[
     98  @item{a programmer may specify @racket[syntax-parse]
     99   options, e.g., @racket[#:datum-literals];}
    100  @item{pattern positions may use any @racket[syntax-parse] combinators, e.g. 
    101   @racket[~and], @racket[~seq], or custom-defined @tech:pat-expanders;}
    102  @item{and the premises may be interleaved with @racket[syntax-parse]
    103   @tech:pat-directives, e.g., @racket[#:with] or @racket[#:when].}]
    104 
    105 @; Subsec: Type rules vs define-typed-syntax ----------------------------------
    106 @subsection{Type rules vs @racket[define-typed-syntax]}
    107 
    108 The @racket[define-typed-syntax] form extends typical Racket macros by
    109 interleaving type checking computations, possibly written using a type judgement
    110 syntax, directly into the macro definition.
    111 
    112 Compared to the type rules in the @secref{judgements} section, Turnstile
    113 @racket[define-typed-syntax] definitions differ in a few ways:
    114 
    115 @itemlist[ @item{Each premise and conclusion must be enclosed in brackets.}
    116 
    117 @item{A conclusion is "split" into its inputs (at the top) and outputs (at the
    118 bottom) to resemble other Racket macro-definition forms. In other words,
    119 pattern variable scope flows top-to-bottom, enabling the programmers to read
    120 the code more easily.
    121 
    122   For example, the input part of the [LAM] rule's conclusion corresponds to the
    123 @racket[(λ ([x:id : τ_in:type] ...) e)] pattern and the output part
    124 corresponds to the @racket[(λ- (x- ...) e-)] and @racket[(→ τ_in.norm
    125 ... τ_out)] templates. A @racket[≫] delimiter separates the input pattern
    126 from the premises while @racket[⇒] in the conclusion associates the type
    127 with the output expression.}
    128 
    129  @item{The @racket[define-typed-syntax] definitions do not thread through an
    130 explicit type environment @racket[Γ].  Rather, Turnstile reuses Racket's
    131 lexical scope as the type environment and programmers should only write new
    132 type environment bindings to the left of the @racket[⊢], analogous to
    133 @racket[let].}
    134 
    135  @item{Since type environments obey lexical scope, an explicit implementation of
    136   the @tt{[VAR]} rule is unneeded.}]
    137 
    138 @; Subsec: define-typed-syntax premises ---------------------------------------
    139 @subsection{@racket[define-typed-syntax] premises}
    140 
    141 Like their type rule counterparts, a @racket[define-typed-syntax] definition
    142 supports two @cite{bidirectional}-style type checking judgements in its
    143 premises.
    144 
    145 A @racket[[⊢ e ≫ e- ⇒ τ]] judgement expands expression @racket[e], binds its
    146 expanded form to @racket[e-], and its type to @racket[τ]. In other words, 
    147 @racket[e] is an input syntax template, and @racket[e-] and @racket[τ] are
    148 output patterns.
    149 
    150 Dually, one may write @racket[[⊢ e ≫ e- ⇐ τ]] to check that @racket[e] has type
    151 @racket[τ]. Here, both @racket[e] and @racket[τ] are inputs (templates) and only
    152  @racket[e-] is an output (pattern).
    153 
    154 For example, in the definition of @code{#%app} from section
    155 @secref{define-typed-syntax}, the first premise, @racket[[⊢ e_fn ≫ e_fn- ⇒ (~→
    156 τ_in ... τ_out)]], expands function @racket[e_fn], binds it to pattern variable
    157 @racket[e_fn-], and binds its input types to @racket[(τ_in ...)] and its output
    158 type to @racket[τ_out]. Macro expansion stops with a type error if
    159 @racket[e_fn] does not have a function type.
    160 
    161 The second @racket[#%app] premise then uses the @racket[⇐] to check that the
    162 given inputs have types that match the expected types. Again, a type error is
    163 reported if the types do not match.
    164 
    165 The @racket[λ] definition from that section also utilizes a @racket[⇒] premise,
    166 except it must add bindings to the type environment, which are written to the
    167 left of the @racket[⊢]. The lambda body is then type checked in this context.
    168 
    169 Observe how ellipses may be used in exactly the same manner as
    170 other Racket macros. (The @racket[norm] @tech:attribute comes from the
    171 @racket[type] syntax class and is bound to the expanded representation of the
    172 type. This is used to avoid double-expansions of the types.)
    173 
    174 @; Subsec: syntax-parse directives as premises --------------------------------
    175 @subsection{@racket[syntax-parse] directives as premises}
    176 
    177 A @racket[define-typed-syntax] definition may also use @racket[syntax-parse]
    178 options and @|tech:pat-directives| in its premises. Here is a modified
    179 @racket[#%app] that reports a more precise error for an arity mismatch:
    180 
    181 @label-code["Function application with a better error message" @#reader
    182 scribble/comment-reader
    183 (racketblock0
    184 ;; [APP]
    185 (define-typed-syntax (#%app e_fn e_arg ...) ≫
    186   [⊢ e_fn ≫ e_fn- ⇒ (~→ τ_in ... τ_out)]
    187   #:fail-unless (stx-length=? #'[τ_in ...] #'[e_arg ...])
    188                 (format "arity mismatch, expected ~a args, given ~a"
    189                         (stx-length #'[τ_in ...]) #'[e_arg ...])
    190   [⊢ e_arg ≫ e_arg- ⇐ τ_in] ...
    191   --------
    192   [⊢ (#%app- e_fn- e_arg- ...) ⇒ τ_out]))]
    193 
    194 @; Sec: Defining Types --------------------------------------------------------
    195 @section[#:tag "tycon"]{Defining Types}
    196 
    197 The rules from section @secref{define-typed-syntax} require a function type
    198 constructor. Turnstile includes convenient forms for defining such type
    199 constructors, e.g. @racket[define-type-constructor]:
    200 
    201 @label-code["The function type"
    202 (racketblock0
    203  (define-type-constructor → #:arity > 0))]
    204 
    205 The @racket[define-type-constructor] declaration defines the @racket[→]
    206 function type as a macro that takes at least one argument, along with a
    207 @racket[~→] @tech:pat-expander matching on that type in @|tech:stx-pats|.
    208 
    209 @; Sec: Defining check rules---------------------------------------------------
    210 @section{Defining @racket[⇐] Rules}
    211 
    212 The rules from from @secref{judgements} are incomplete. Specifically,
    213 @\racket[⇐] clauses appear in the premises but never in the conclusion.
    214 
    215 To complete the rules, we can add a general @racket[⇐] rule (sometimes called a
    216 subsumption rule) that dispatches to the appropriate @racket[⇒] rule:
    217 
    218 @verbatim|{
    219        Γ ⊢ e ≫ e- ⇒ τ2
    220        τ1 = τ2
    221 [FLIP] -----------------
    222        Γ ⊢ e ≫ e- ⇐ τ1
    223 }|
    224 
    225 Similarly, Turnstile uses an implicit @racket[⇐] rule so programmers need not
    226 specify a @racket[⇐] variant of every rule. If a programmer writes an explicit
    227 @racket[⇐] rule, then it is used instead of the default. Writing an explicit
    228 @racket[⇐] rule is useful for implementing (local) type inference or type
    229 annotations. Here is an extended lambda that adds a
    230 @racket[⇐] clause.
    231 
    232 @label-code["lambda with inference, and ann"
    233 @#reader scribble/comment-reader
    234 (racketblock0
    235 ;; [LAM]
    236 (define-typed-syntax λ #:datum-literals (:)
    237   ;; ⇒ rule (as before)
    238   [(_ ([x:id : τ_in:type] ...) e) ≫
    239    [[x ≫ x- : τ_in.norm] ... ⊢ e ≫ e- ⇒ τ_out]
    240    -------
    241    [⊢ (λ- (x- ...) e-) ⇒ (→ τ_in.norm ... τ_out)]]
    242   ;; ⇐ rule (new)
    243   [(_ (x:id ...) e) ⇐ (~→ τ_in ... τ_out) ≫
    244    [[x ≫ x- : τ_in] ... ⊢ e ≫ e- ⇐ τ_out]
    245    ---------
    246    [⊢ (λ- (x- ...) e-)]])
    247 
    248 (define-typed-syntax (ann e (~datum :) τ:type) ≫
    249   [⊢ e ≫ e- ⇐ τ.norm]
    250   --------
    251   [⊢ e- ⇒ τ.norm]))]
    252 
    253 This revised lambda definition uses an alternate, multi-clause 
    254 @racket[define-typed-syntax] syntax, analogous to @racket[syntax-parse] (whereas
    255 @seclink["define-typed-syntax"]{the simpler syntax from section 1.2} resembles
    256 @racket[define-simple-macro]).
    257 
    258 The first clause is the same as before. The second clause has an additional
    259 input type pattern and the clause matches only if both patterns match,
    260 indicating that the type of the expression can be inferred. Observe that the
    261 second lambda clause's input parameters have no type annotations. Since the
    262 lambda body's type is already known, the premise in the second clause uses the 
    263 @racket[⇐] arrow. Finally, the conclusion specifies only the expanded
    264 syntax object because the input type is automatically attached to that output.
    265 
    266 We also define an annotation form @racket[ann], which invokes the @racket[⇐]
    267 clause of a type rule.
    268 
    269 @; Sec: Defining primitive ops ------------------------------------------------
    270 @section{Defining Primitive Operations (Primops)}
    271 
    272 The previous sections have defined type rules for @racket[#%app] and @racket[λ],
    273 as well as a function type, but we cannot write any well-typed programs yet
    274 since there are no base types. Let's fix that:
    275 
    276 @label-code["defining a base type, literal, and primop"
    277 @#reader scribble/comment-reader
    278 (racketblock0
    279 (define-base-type Int)
    280 
    281 (define-primop + : (→ Int Int Int))
    282 
    283 (define-typed-syntax #%datum
    284   [(_ . n:integer) ≫
    285    --------
    286    [⊢ (#%datum- . n) ⇒ Int]]
    287   [(_ . x) ≫
    288    --------
    289    [#:error (type-error #:src #'x
    290                         #:msg "Unsupported literal: ~v" #'x)]]))]
    291 
    292 The code above defines a base type @racket[Int], and attaches type information
    293 to both @racket[+] and integer literals.
    294 
    295 @racket[define-primop] creates an identifier macro that attaches the specified
    296 type to the specified identifier. When only one identifier is specified, it is
    297 used as both the name of the typed operation, and appended with a "@tt{-}"
    298 suffix and (that corresponding Racket function is) used as the macro
    299 output. Alternatively, a programmer may explicitly specify separate surface and
    300 target identifiers (see @racket[define-primop] in the reference).
    301 
    302 @; Sec: A Complete Language  --------------------------------------------------
    303 @section[#:tag "stlc"]{A Complete Language}
    304 
    305 We can now write well-typed programs! Here is the complete
    306 language implementation, with some examples:
    307 
    308 @margin-note{Languages implemented using @hash-lang[] @racketmodname[turnstile]
    309 must additionally provide @racket[#%module-begin] and other forms required by
    310 Racket. Use @hash-lang[] @racketmodname[turnstile]@tt{/lang} to automatically
    311 provide some default forms. See the section on @secref{turnstilelang} for more
    312 details.}
    313 
    314 @; using `racketmod` because `examples` doesnt work with provide
    315 @(racketmod0 #:file "STLC"
    316 turnstile
    317 (provide → Int λ #%app #%datum + ann)
    318 
    319 (define-base-type Int)
    320 (define-type-constructor → #:arity > 0)
    321  
    322 (define-primop + : (→ Int Int Int))
    323 
    324 (code:comment "[APP]")
    325 (define-typed-syntax (#%app e_fn e_arg ...) ≫
    326   [⊢ e_fn ≫ e_fn- ⇒ (~→ τ_in ... τ_out)]
    327   #:fail-unless (stx-length=? #'[τ_in ...] #'[e_arg ...])
    328                 (format "arity mismatch, expected ~a args, given ~a"
    329                         (stx-length #'[τ_in ...]) #'[e_arg ...])
    330   [⊢ e_arg ≫ e_arg- ⇐ τ_in] ...
    331   --------
    332   [⊢ (#%app- e_fn- e_arg- ...) ⇒ τ_out])
    333  
    334 (code:comment "[LAM]")
    335 (define-typed-syntax λ #:datum-literals (:)
    336   [(_ ([x:id : τ_in:type] ...) e) ≫
    337    [[x ≫ x- : τ_in.norm] ... ⊢ e ≫ e- ⇒ τ_out]
    338    -------
    339    [⊢ (λ- (x- ...) e-) ⇒ (→ τ_in.norm ... τ_out)]]
    340   [(_ (x:id ...) e) ⇐ (~→ τ_in ... τ_out) ≫
    341    [[x ≫ x- : τ_in] ... ⊢ e ≫ e- ⇐ τ_out]
    342    ---------
    343    [⊢ (λ- (x- ...) e-)]])
    344 
    345 (code:comment "[ANN]")
    346 (define-typed-syntax (ann e (~datum :) τ:type) ≫
    347   [⊢ e ≫ e- ⇐ τ.norm]
    348   --------
    349   [⊢ e- ⇒ τ.norm])
    350 
    351 (code:comment "[DATUM]")
    352 (define-typed-syntax #%datum
    353   [(_ . n:integer) ≫
    354    --------
    355    [⊢ (#%datum- . n) ⇒ Int]]
    356   [(_ . x) ≫
    357    --------
    358    [#:error (type-error #:src #'x
    359                         #:msg "Unsupported literal: ~v" #'x)]]))
    360 
    361 @;TODO: how to run examples with the typeset code?
    362 @(define stlc-eval
    363   (parameterize ([sandbox-output 'string]
    364                  [sandbox-error-output 'string]
    365                  [sandbox-eval-limits #f])
    366     (make-base-eval #:lang 'turnstile/examples/stlc+lit)))
    367 
    368 @examples[#:eval stlc-eval #:label "STLC Examples:" #:no-inset
    369   1
    370   (eval:error "1")
    371   (+ 1 2)
    372   (eval:error (+ 1 (λ ([x : Int]) x)))
    373   (eval:error (λ (x) x))
    374   (ann (λ (x) x) : (→ Int Int))
    375   ((ann (λ (x) x) : (→ Int Int)) 1)
    376   (((λ ([f : (→ Int Int Int)]) 
    377       (λ ([x : Int][y : Int]) 
    378         (f x y))) 
    379     +) 
    380    1 2)
    381   ]
    382 
    383 
    384 @; Sec: Extending a Language  -------------------------------------------------
    385 @section[#:tag "stlcsub"]{Extending a Language}
    386 
    387 Since the @tt{STLC} language from @secref{stlc} is implemented as just a series
    388 of macros, like any other Racket @hash-lang[], its forms may be imported and
    389 exported and may be easily reused in other languages. Let's see how we can
    390 reuse the above implementation to implement a subtyping language.
    391 
    392 @(racketmod0 #:file "STLC+SUB" #:escape UNSYNTAX
    393 turnstile
    394 (extends STLC #:except #%datum +)
    395 
    396 (provide Top Num Nat + add1 #%datum if)
    397 
    398 (define-base-types Top Num Nat)
    399 
    400 (define-primop + : (→ Num Num Num))
    401 (define-primop add1 : (→ Int Int))
    402 
    403 (define-typed-syntax #%datum
    404   [(_ . n:nat) ≫
    405    --------
    406    [⊢ (#%datum- . n) ⇒ Nat]]
    407   [(_ . n:integer) ≫
    408    --------
    409    [⊢ (#%datum- . n) ⇒ Int]]
    410   [(_ . n:number) ≫
    411    --------
    412    [⊢ (#%datum- . n) ⇒ Num]]
    413   [(_ . x) ≫
    414    --------
    415    [≻ (STLC:#%datum . x)]])
    416 
    417 (begin-for-syntax
    418   (define (sub? t1 t2)
    419     (code:comment "need this because recursive calls made with unexpanded types")
    420     (define τ1 ((current-type-eval) t1))
    421     (define τ2 ((current-type-eval) t2))
    422     (or ((current-type=?) τ1 τ2)
    423         (Top? τ2)
    424         (syntax-parse (list τ1 τ2)
    425           [(_ ~Num) ((current-sub?) τ1 #'Int)]
    426           [(_ ~Int) ((current-sub?) τ1 #'Nat)]
    427           [((~→ τi1 ... τo1) (~→ τi2 ... τo2))
    428            (and (subs? #'(τi2 ...) #'(τi1 ...))
    429                 ((current-sub?) #'τo1 #'τo2))]
    430           [_ #f])))
    431   (define current-sub? (make-parameter sub?))
    432   (current-typecheck-relation sub?)
    433   (define (subs? τs1 τs2)
    434     (and (stx-length=? τs1 τs2)
    435          (stx-andmap (current-sub?) τs1 τs2)))
    436   
    437   (define (join t1 t2)
    438     (cond
    439       [((current-sub?) t1 t2) t2]
    440       [((current-sub?) t2 t1) t1]
    441       [else #'Top]))
    442   (define current-join (make-parameter join)))
    443 
    444 (code:comment "[IF]")
    445 (define-typed-syntax (if e_tst e1 e2) ≫
    446   [⊢ e_tst ≫ e_tst- ⇒ _] (code:comment "a non-false value is truthy")
    447   [⊢ e1 ≫ e1- ⇒ τ1]
    448   [⊢ e2 ≫ e2- ⇒ τ2]
    449   --------
    450   [⊢ (if- e_tst- e1- e2-) ⇒ #,((current-join) #'τ1 #'τ2)]))
    451 
    452 This language uses subtyping instead of type equality as its "typecheck
    453 relation". Specifically, the language defines a @racket[sub?] function
    454 and sets it as the @racket[current-typecheck-relation]. Thus it is able to reuse
    455 the @racket[λ] and @racket[#%app] rules from the previous sections without
    456 modification. The @racket[extends] clause is useful for declaring this. It
    457 automatically @racket[require]s and @racket[provide]s the previous rules and
    458 re-exports them with the new language.
    459 
    460 The new language does not reuse @racket[#%datum] and @racket[+], however, since
    461 subtyping requires updates these forms. Specifically, the new language defines
    462 a hierarchy of numeric base types, gives @racket[+] a more general type, and
    463 redefines @racket[#%datum] to assign more precise types to numeric literals.
    464 Observe that @racket[#%datum] dispatches to @tt{STLC}'s datum in the "else"
    465 clause, using the @racket[≻] conclusion form, which dispatches to another
    466 (typed) macro. In this manner, the new typed language may still define and
    467 invoke macros like any other Racket program.
    468 
    469 Since the new language uses subtyping, it also defines a (naive) @racket[join]
    470 function, which is needed by conditional forms like @racket[if]. The 
    471 @racket[if] definition uses the @racket[current-join] parameter, to
    472 make it reusable by other languages. Observe that the output type in the 
    473 @racket[if] definition uses @racket[unquote]. In general, all @tech:stx-template
    474 positions in Turnstile are @racket[quasisyntax]es.
    475 
    476 @(define stlc+sub-eval
    477   (parameterize ([sandbox-output 'string]
    478                  [sandbox-error-output 'string]
    479                  [sandbox-eval-limits #f])
    480     (make-base-eval #:lang 'turnstile/examples/stlc+sub)))
    481 
    482 @examples[#:eval stlc+sub-eval #:label "STLC+SUB Examples:" #:no-inset
    483  ((λ ([x : Top]) x) -1)
    484  ((λ ([x : Num]) x) -1)
    485  ((λ ([x : Int]) x) -1)
    486  (eval:error ((λ ([x : Nat]) x) -1))
    487  ((λ ([f : (→ Int Int)]) (f -1)) add1)
    488  ((λ ([f : (→ Nat Int)]) (f 1)) add1)
    489  (eval:error ((λ ([f : (→ Num Int)]) (f 1.1)) add1))
    490  ((λ ([f : (→ Nat Num)]) (f 1)) add1)
    491  (eval:error ((λ ([f : (→ Int Nat)]) (f 1)) add1))
    492  (eval:error ((λ ([f : (→ Int Int)]) (f 1.1)) add1))
    493   ]
    494