macrotypes/turnstile/scribblings/reference.scrbl

540 lines
24 KiB
Racket

#lang scribble/manual
@(require scribble/example racket/sandbox
(for-label racket/base
(except-in turnstile/turnstile stx mk-~ mk-?))
"doc-utils.rkt" "common.rkt")
@title{The Turnstile Reference}
@section{Typing Unicode Characters}
@(define the-eval
(parameterize ([sandbox-output 'string]
[sandbox-error-output 'string]
[sandbox-eval-limits #f])
(make-base-eval #:lang 'turnstile)))
@; insert link?
@; https://docs.racket-lang.org/drracket/Keyboard_Shortcuts.html
Turnstile utilizes unicode. Here are DrRacket keyboard shortcuts for the
relevant characters. Type (any unique prefix of) the following
and then press Control-@litchar{\}.
@itemlist[
@item{@litchar{\vdash} @litchar{}}
@item{@litchar{\gg} @litchar{}}
@item{@litchar{\rightarrow} @litchar{}}
@item{@litchar{\Rightarrow} @litchar{}}
@item{@litchar{\Leftarrow} @litchar{}}
@item{@litchar{\succ} @litchar{}}
]
@section{Forms}
@; define-typed-syntax---------------------------------------------------------
@defform*[
#:literals ( : --------)
((define-typed-syntax (name-id . pattern)
premise ...
--------
conclusion)
(define-typed-syntax name-id option ... rule ...+))
#:grammar
([option (code:line @#,racket[syntax-parse] option)]
[rule [expr-pattern
premise ...
--------
conclusion]
[expr-pattern type-pattern
premise ...
--------
⇐-conclusion]
[expr-pattern key type-pattern
premise ...
--------
⇐-conclusion]]
[expr-pattern (code:line @#,racket[syntax-parse] @#,tech:stx-pat)]
[type-pattern (code:line @#,racket[syntax-parse] @#,tech:stx-pat)]
[expr-template (code:line @#,racket[quasisyntax] @#,tech:template)]
[type-template (code:line @#,racket[quasisyntax] @#,tech:template)]
[key identifier?]
[premise (code:line [ tc ...] ooo ...)
(code:line [ctx tc ...] ooo ...)
(code:line [ctx-elem ... tc ...] ooo ...)
(code:line [ctx ctx tc ...] ooo ...)
(code:line [ . tc-elem] ooo ...)
(code:line [ctx . tc-elem] ooo ...)
(code:line [ctx-elem ... . tc-elem] ooo ...)
(code:line [ctx ctx . tc-elem] ooo ...)
type-relation
(code:line @#,racket[syntax-parse] @#,tech:pat-directive)]
[ctx (ctx-elem ...)]
[ctx-elem (code:line [id id : type-template] ooo ...)]
[tc (code:line tc-elem ooo ...)]
[tc-elem [expr-template expr-pattern type-pattern]
[expr-template expr-pattern key type-pattern]
[expr-template expr-pattern ( key type-pattern) ooo ...]
[expr-template expr-pattern type-template]
[expr-template expr-pattern key type-template]
[expr-template expr-pattern ( key type-template) ooo ...]]
[type-relation (code:line [type-template τ= type-template] ooo ...)
(code:line [type-template τ= type-template #:for expr-template] ooo ...)
(code:line [type-template τ⊑ type-template] ooo ...)
(code:line [type-template τ⊑ type-template #:for expr-template] ooo ...)]
[conclusion [ expr-template type-template]
[ expr-template key type-template]
[ expr-template ( key type-template) ooo ...]
[ expr-template]
[#:error expr-template]]
[⇐-conclusion [ expr-template]]
[ooo ...])
]{
Defines a macro that additionally performs typechecking. It uses
@racket[syntax-parse] @tech:stx-pats and @tech:pat-directives and
additionally allows writing type-judgement-like clauses that interleave
typechecking and macro expansion.
Type checking is computed as part of macro expansion and the resulting type is
attached to an expanded expression. In addition, Turnstile supports
bidirectional type checking clauses. For example @racket[[ e e- τ]]
declares that expression @racket[e] expands to @racket[e-] and has type
@racket[τ], where @racket[e] is the input and, @racket[e-] and @racket[τ]
outputs. Syntactically, @racket[e] is a syntax template position and
@racket[e-] @racket[τ] are syntax pattern positions.
A programmer may use the generalized form @racket[[ e e- ( key τ) ...]] to
specify propagation of arbitrary values, associated with any number of
keys. For example, a type and effect system may wish to additionally propagate
source locations of allocations and mutations. When no key is specified,
@litchar{:}, i.e., the "type" key, is used. Dually, one may write @racket[[ e
e- τ]] to check that @racket[e] has type @racket[τ]. Here, both @racket[e]
and @racket[τ] are inputs (templates) and only @racket[e-] is an
output (pattern).
The @racket[] conclusion form is useful in many scenarios where the rule being
implemented may not want to attach type information. E.g.,
@itemlist[#:style 'ordered
@item{when a rule's output is another typed macro.
For example, here is a hypothetical @tt{typed-let*} that is implemented in
terms of a @tt{typed-let}:
@racketblock0[
(define-typed-syntax typed-let*
[(_ () e_body)
--------
[ e_body]]
[(_ ([x e] [x_rst e_rst] ...) e_body)
--------
[ (typed-let ([x e]) (typed-let* ([x_rst e_rst] ...) e_body))]])]
The conclusion in the first clause utilizes @racket[] since the body already
carries its own type. The second clause uses @racket[] because it defers to
@tt{typed-let}, which will attach type information.}
@item{when a rule extends another.
This is related to the first example. For example, here is a @racket[#%datum]
that extends another with more typed literals (see also @secref{stlcsub}).
@racketblock0[
(define-typed-syntax typed-datum
[(_ . n:integer)
--------
[ (#%datum- . n) Int]]
[(_ . x)
--------
[#:error (type-error #:src #'x #:msg "Unsupported literal: ~v" #'x)]])
(define-typed-syntax extended-datum
[(_ . s:str)
--------
[ (#%datum- . s) String]]
[(_ . x)
--------
[ (typed-datum . x)]])]}
@item{for top-level forms.
For example, here is a basic typed version of @racket[define]:
@racketblock0[
(define-typed-syntax define
[(_ x:id e)
[ e e- τ]
#:with y (generate-temporary #'x)
--------
[ (begin-
(define-syntax x (make-rename-transformer ( y : τ)))
(define- y e-))]])]
This macro creates an indirection @racket[make-rename-transformer] in order to
attach type information to the top-level @tt{x} identifier, so the
@racket[define] forms themselves do not need type information.}
]}
@defform[(define-typerule ....)]{Alias for @racket[define-typed-syntax].}
@defform[(define-syntax/typecheck ....)]{Alias for @racket[define-typed-syntax].}
@; syntax-parse/typecheck------------------------------------------------------
@defform[(syntax-parse/typecheck stx option ... rule ...+)]{
A @racket[syntax-parse]-like form that supports
@racket[define-typed-syntax]-style clauses. In particular, see the
"rule" part of @racket[define-typed-syntax]'s grammar above.}
@; define-primop --------------------------------------------------------------
@defform*[((define-primop typed-op-id τ)
(define-primop typed-op-id : τ)
(define-primop typed-op-id op-id τ)
(define-primop typed-op-id op-id : τ))]{
Defines @racket[typed-op-id] by attaching type @racket[τ] to (untyped)
identifier @racket[op-id], e.g.:
@racketblock[(define-primop typed+ + : ( Int Int))]
When not specified, @racket[op-id] is @racket[typed-op-id] suffixed with
@litchar{-} (see @secref{racket-}).}
@; define-syntax-category -----------------------------------------------------
@defform[(define-syntax-category name-id)]{
Defines a new "category" of syntax by defining a series of forms and functions.
Turnstile pre-declares @racket[(define-syntax-category type)], which in turn
defines the following forms and functions:
@margin-note{It's not important to immediately understand all these
definitions. Some, like @racket[type?] and @racket[mk-type], are
more "low-level" and are mainly used by the other forms. The most useful forms
are probably @racket[define-typed-syntax], and the type-defining forms
@racket[define-base-type], @racket[define-type-constructor], and
@racket[define-binding-type].}
@itemlist[
@item{@racket[define-typed-syntax], as described above.
Uses @racket[current-typecheck-relation] for typechecking.}
@item{@defform*[((define-base-type base-type-name-id)
(define-base-type base-type-name-id : kind))]{
Defines a base type. @racket[(define-base-type τ)] in turn defines:
@itemlist[@item{@racket[τ], an identifier macro representing type @racket[τ].}
@item{@racket[τ?], a phase 1 predicate recognizing type @racket[τ].}
@item{@racket[], a phase 1 @tech:pat-expander recognizing type @racket[τ].}]}
The second form is useful when implementing your own kind system.
@racket[#%type] is used as the @tt{kind} when it's not specified.}
@item{@defform[(define-base-types base-type-name-id ...)]{Defines multiple base types.}}
@item{
@defform[(define-type-constructor name-id option ...)
#:grammar
([option (code:line #:arity op n)
(code:line #:arg-variances expr)
(code:line #:extra-info stx)])]{
Defines a type constructor that does not bind type variables.
Defining a type constructor @racket[τ] defines:
@itemlist[@item{@racket[τ], a macro for constructing an instance of type
@racket[τ], with the specified arity.
Validates inputs and expands to @racket[τ-], attaching kind.}
@item{@racket[τ-], an internal macro that expands to the internal
(i.e., fully expanded) type representation. Does not validate
inputs or attach kinds. This macro is useful when creating
a separate kind system, see @racket[define-internal-type-constructor].}
@item{@racket[τ?], a phase 1 predicate recognizing type @racket[τ].}
@item{@racket[], a phase 1 @tech:pat-expander recognizing type @racket[τ].}]
The @racket[#:arity] argument specifies the valid shapes
for the type. For example
@racket[(define-type-constructor #:arity >= 1)] defines an arrow type and
@racket[(define-type-constructor Pair #:arity = 2)] defines a pair type.
The default arity is @racket[= 1].
The @racket[#:arg-variances] argument is a transformer converting a syntax
object of the type to a list of variances for the arguments to the type
constructor.
The possible variances are @racket[invariant], @racket[contravariant],
@racket[covariant], and @racket[irrelevant].
If @racket[#:arg-variances] is not specified, @racket[invariant] is used for
all positions.
Example:
@racketblock0[(define-type-constructor #:arity >= 1
#:arg-variances
(λ (stx)
(syntax-parse stx
[(_ τ_in ... τ_out)
(append
(make-list (stx-length #'[τ_in ...]) contravariant)
(list covariant))])))]
The @racket[#:extra-info] argument is useful for attaching additional
metainformation to types, for example to implement pattern matching.}}
@item{
@defform[(define-internal-type-constructor name-id option ...)
#:grammar
([option (code:line #:arity op n)
(code:line #:arg-variances expr)
(code:line #:extra-info stx)])]{
Like @racket[define-type-constructor], except the surface macro is not defined.
Use this form, for example, when creating a separate kind system so that
you can still use other Turnstile conveniences like pattern expanders.}}
@item{
@defform[(define-binding-type name-id option ...)
#:grammar
([option (code:line #:arity op n)
(code:line #:bvs op n)
(code:line #:arr kindcon)
(code:line #:arg-variances expr)
(code:line #:extra-info stx)])]{
Similar to @racket[define-type-constructor], except
@racket[define-binding-type] defines a type that binds type variables.
Defining a type constructor @racket[τ] defines:
The @racket[#:arity] and @racket[#:bvs] arguments specify the valid shapes
for the type. For example
@racket[(define-binding-type #:arity = 1 #:bvs = 1)] defines a type
with shape @racket[( (X) τ)], where @racket[τ] may reference @racket[X].
The default @racket[#:arity] is @racket[= 1]
and the default @racket[#:bvs] is @racket[>= 0].
Use the @racket[#:arr] argument to define a type with kind annotations
on the type variables. The @racket[#:arr] argument is an "arrow" that "saves"
the annotations after a type is expanded and annotations are erased,
analogous to how "saves" the type annotations on a lambda.}}
@item{
@defform[(define-internal-binding-type name-id option ...)
#:grammar
([option (code:line #:arity op n)
(code:line #:bvs op n)
(code:line #:arr kindcon)
(code:line #:arg-variances expr)
(code:line #:extra-info stx)])]{
Analogous to @racket[define-internal-type-constructor].}}
@item{
@defform[(type-out ty-id)]{
A @racket[provide]-spec that, given @racket[ty-id], provides @racket[ty-id],
and provides @racket[for-syntax] a predicate @racket[ty-id?] and a @tech:pat-expander @racket[~ty-id].}}
@item{@defparam[current-type-eval type-eval type-eval]{
A phase 1 parameter for controlling "type evaluation". A @racket[type-eval]
function consumes and produces syntax. It is typically used to convert a type
into a canonical representation. The @racket[(current-type-eval)] is called
immediately before attacing a type to a syntax object, i.e., by
@racket[assign-type].
It defaults to full expansion, i.e., @racket[(lambda (stx) (local-expand stx 'expression null))], and also stores extra surface syntax information used for error reporting.
One should extend @racket[current-type-eval] if canonicalization of types
depends on combinations of different types, e.g., type lambdas and type application in F-omega. }}
@; current-typecheck-relation -------------------------------------------------
@item{@defparam[current-typecheck-relation type-pred type-pred]{
A phase 1 parameter for controlling type checking, used by
@racket[define-typed-syntax]. A @racket[type-pred] function consumes two
types---the first is the given type and the second is the expected type---and
returns true if they "type check". It defaults to @racket[type=?] though it
does not have to be a symmetric relation. Useful for reusing rules that differ
only in the type check operation, e.g., equality vs subtyping.}}
@item{@defproc[(typecheck? [τ1 type?] [τ2 type?]) boolean?]{
A phase 1 function that calls @racket[current-typecheck-relation]. The first
argument is the given type and the second is the expected type.}}
@item{@defproc[(typechecks? [τs1 (or/c (list/c type?) (stx-list/c type?))]
[τs2 (or/c (list/c type?) (stx-list/c type?))]) boolean?]{
Phase 1 function mapping @racket[typecheck?] over lists of types,
pairwise. @racket[τs1] and @racket[τs2] must have the same length. The first
list contains the given types and the second list contains the expected
types.}}
@item{@defproc[(type=? [τ1 type?] [τ2 type?]) boolean?]{A phase 1 equality
predicate for types that computes structural, @racket[free-identifier=?]
equality, but includes alpha-equivalence.
@examples[#:eval the-eval
(define-base-type Int)
(define-base-type String)
(begin-for-syntax (displayln (type=? #'Int #'Int)))
(begin-for-syntax (displayln (type=? #'Int #'String)))
(define-type-constructor #:arity > 0)
(define-binding-type #:arity = 1 #:bvs = 1)
(begin-for-syntax
(displayln
(type=? ((current-type-eval) #'( (X) X))
((current-type-eval) #'( (Y) Y)))))
(begin-for-syntax
(displayln
(type=? ((current-type-eval) #'( (X) ( (Y) ( X Y))))
((current-type-eval) #'( (Y) ( (X) ( Y X)))))))
(begin-for-syntax
(displayln
(type=? ((current-type-eval) #'( (Y) ( (X) ( Y X))))
((current-type-eval) #'( (X) ( (X) ( X X)))))))
]
}}
@item{@defproc[(types=? [τs1 (listof type?)][τs2 (listof type?)]) boolean?]{
A phase 1 predicate checking that @racket[τs1] and @racket[τs2] are equivalent, pairwise. Thus,
@racket[τs1] and @racket[τs2] must have the same length.}}
@item{@defproc[(same-types? [τs (listof type?)]) boolean?]{
A phase 1 predicate for checking if a list of types are all the same.}}
@item{@defparam[current-type=? binary-type-pred binary-type-pred]{
A phase 1 parameter for computing type equality. Is initialized to @racket[type=?].}}
@item{@defidform[#%type]{The default "kind" used to validate types.}}
@item{@defproc[(#%type? [x Any]) boolean?]{Phase 1 predicate recognizing @racket[#%type].}}
@item{@defproc[(type? [x Any]) boolean?]{A phase 1 predicate recognizing well-formed types.
Checks that @racket[x] is a syntax object and has syntax propety @racket[#%type].}}
@item{@defparam[current-type? type-pred type-pred]{A phase 1 parameter, initialized to @racket[type?],
used to recognize well-formed types.
Useful when reusing type rules in different languages. For example,
System F-omega may define this to recognize types with "star" kinds.}}
@item{@defproc[(any-type? [x Any]) boolean?]{A phase 1 predicate recognizing valid types.
Defaults to @racket[type?].}}
@item{@defparam[current-any-type? type-pred type-pred]{A phase 1 parameter,
initialized to @racket[any-type?],
used to validate (not necessarily well-formed) types.
Useful when reusing type rules in different languages. For example,
System F-omega may define this to recognize types with a any valid kind,
whereas @racket[current-type?] would recognize types with only the "star" kind.}}
@item{@defproc[(mk-type [stx syntax?]) type?]{
Phase 1 function that marks a syntax object as a type by attaching @racket[#%type].
Useful for defining your own type with arbitrary syntax that does not fit with
@racket[define-base-type] or @racket[define-type-constructor].}}
@item{@defthing[type stx-class]{A syntax class that calls @racket[current-type?]
to validate well-formed types.
Binds a @racket[norm] attribute to the type's expanded representation.}}
@item{@defthing[any-type stx-class]{A syntax class that calls @racket[current-any-type?]
to validate types.
Binds a @racket[norm] attribute to the type's expanded representation.}}
@item{@defthing[type-bind stx-class]{A syntax class recognizing
syntax objects with shape @racket[[x:id (~datum :) τ:type]].}}
@item{@defthing[type-ctx stx-class]{A syntax class recognizing
syntax objects with shape @racket[(b:type-bind ...)].}}
@item{@defthing[type-ann stx-class]{A syntax class recognizing
syntax objects with shape @racket[{τ:type}] where the braces are required.}}
]
}
@section{@racket[require] and @racket[provide]-related Forms}
@defform[(typed-out x+ty+maybe-rename ...)
#:grammar
([x+ty+maybe-rename
(code:line [x ty])
(code:line [x : ty])
(code:line [[x ty] out-x])
(code:line [[x : ty] out-x])]
[x identifier?]
[out-x identifier?]
[ty type?])]{
A provide-spec that adds type @racket[ty] to untyped @racket[x] and provides
that typed identifier as either @racket[x], or @racket[out-x] if it's specified.
Equivalent to a @racket[define-primop] that automatically provides its
definition.}
@defform[(extends base-lang option ...)
#:grammar
([option (code:line #:except id ...)
(code:line #:rename [old new] ...)])]{
Requires all forms from @racket[base-lang] and reexports them. Tries to
automatically handle conflicts for commonly used forms like @racket[#%app].
The imported names are available for use in the current module, with a
@tt{base-lang:} prefix.}
@defform[(reuse name ... #:from base-lang)
#:grammar
([name id
[old new]])]{
Reuses @racket[name]s from @racket[base-lang].}
@; Sec: Suffixed Racket bindings ----------------------------------------------
@section[#:tag "racket-"]{Suffixed Racket Bindings}
To help avoid name conflicts, Turnstile re-provides all Racket bindings with a
@litchar{-} suffix. These bindings are automatically used in some cases, e.g.,
@racket[define-primop], but in general are useful for avoiding name conflicts.
@; Sec: turnstile/lang ----------------------------------------------
@section[#:tag "turnstilelang"]{@hash-lang[] @racketmodname[turnstile]/lang}
Languages implemented using @hash-lang[] @racketmodname[turnstile]
must additionally provide @racket[#%module-begin] and other forms required by
Racket.
For convenience, Turnstile additionally supplies @hash-lang[]
@racketmodname[turnstile]@tt{/lang}. Languages implemented using this language
will automatically provide Racket's @racket[#%module-begin],
@racket[#%top-interaction], @racket[#%top], and @racket[require].
@; Sec: Lower-level functions -------------------------------------------------
@section{Lower-level Functions}
This section describes lower-level functions and parameters. It's usually not
necessary to call these directly, since @racket[define-typed-syntax] and other
forms already do so, but some type systems may require extending some
functionality.
@defproc[(assign-type [e syntax?] [τ syntax?]) syntax?]{
Phase 1 function that calls @racket[current-type-eval] on @racket[τ] and attaches it to @racket[e]}
@defproc[(typeof [e expr-stx]) type-stx]{
Phase 1 function returning type of @racket[e].}
@defproc[(infer [es (listof expr-stx)]
[#:ctx ctx (listof bindings) null]
[#:tvctx tvctx (listof tyvar-bindings) null]) (list tvs xs es τs)]{
Phase 1 function expanding a list of expressions, in the given contexts and computes their types.
Returns the expanded expressions, their types, and expanded identifiers from the
contexts, e.g. @racket[(infer (list #'(+ x 1)) #:ctx ([x : Int]))].}
@defproc[(subst [τ type-stx]
[x id]
[e expr-stx]
[cmp (-> identifier? identifier? boolean?) bound-identifier=?]) expr-stx]{
Phase 1 function that replaces occurrences of @racket[x], as determined by @racket[cmp], with
@racket[τ] in @racket[e].}
@defproc[(substs [τs (listof type-stx)]
[xs (listof id)]
[e expr-stx]
[cmp (-> identifier? identifier? boolean?) bound-identifier=?]) expr-stx]{
Phase 1 function folding @racket[subst] over the given @racket[τs] and @racket[xs].}
@defform[(type-error #:src srx-stx #:msg msg args ...)]{
Phase 1 form that throws a type error using the specified information. @racket[msg] is a format string that references @racket[args].}
@section{Subtyping}
WARNING: very experimental
Types defined with @racket[define-type-constructor] and
@racket[define-binding-type] may specify variance information and subtyping
languages may use this information to help compute the subtype relation.
The possible variances are:
@defthing[covariant variance?]
@defthing[contravariant variance?]
@defthing[invariant variance?]
@defthing[irrelevant variance?]
@defproc[(variance? [v any/c]) boolean/c]{
Predicate that recognizes the variance values.}
@section{Miscellaneous Syntax Object Functions}
These are all phase 1 functions.
@defproc[(stx-length [stx syntax?]) Nat]{Analogous to @racket[length].}
@defproc[(stx-length=? [stx1 syntax?] [stx2 syntax?]) boolean?]{
Returns true if two syntax objects are of equal length.}
@defproc[(stx-andmap [p? (-> syntax? boolean?)] [stx syntax?]) (listof syntax?)]{
Analogous to @racket[andmap].}