
* add ~typecheck and ~⊢ pattern expanders So that in normal macros, syntax classes, and normal syntax-parse expressions, you can use use the Turnstile syntax to do typechecking * add documentation for ~typecheck and ~⊢
705 lines
30 KiB
Racket
705 lines
30 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 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 key template ... ...] ooo ...)
|
|
(code:line id ooo ...)]
|
|
[tc (code:line tc-elem ooo ...)]
|
|
[tc-elem [expr-template ≫ expr-pattern ⇒ type-pattern]
|
|
[expr-template ≫ expr-pattern ⇒ key pattern]
|
|
[expr-template ≫ expr-pattern (⇒ key pattern) ooo ...]
|
|
[expr-template ≫ expr-pattern ⇐ type-template]
|
|
[expr-template ≫ expr-pattern ⇐ key template]
|
|
[expr-template ≫ expr-pattern (⇐ key 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 template]
|
|
[⊢ expr-template (⇒ key template) ooo ...]
|
|
[≻ expr-template]
|
|
[#:error expr-template]]
|
|
[⇐-conclusion [⊢ expr-template]]
|
|
[ooo ...])
|
|
]{
|
|
|
|
Generates a macro definition that also performs type checking. The macro is
|
|
generated from @racket[syntax-parse] @tech:stx-pats and @tech:pat-directives,
|
|
along with type-judgement-like clauses that interleave typechecking and macro
|
|
expansion. The resulting macro type checks its surface syntax as part of macro
|
|
expansion and the resulting type is attached to the expanded expression.
|
|
|
|
@; ----------------------------------------------------------------------------
|
|
@bold{Premises}
|
|
|
|
@italic{Bidirectional judgements}
|
|
|
|
Turnstile @racket[define-typed-syntax] rules use bidirectional type checking
|
|
judgements:
|
|
@itemlist[
|
|
@item{@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-] and @racket[τ] are syntax pattern positions.}
|
|
|
|
@item{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).}]
|
|
|
|
Each bidirectional arrow has a generalized form that associates a key with a
|
|
value, e.g., @racket[[⊢ e ≫ e- (⇒ key pat) ...]]. A programmer may use this
|
|
generalized form 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.
|
|
|
|
@italic{Contexts}
|
|
|
|
A context may be specified to the left of the turnstile. A context element has
|
|
shape @racket[[⊢ x ≫ x- key pat ... ...]] where @racket[x-] is a pattern
|
|
matching the expansion of @racket[x] and the interleaved @racket[key ...] and
|
|
@racket[pat ...] are arbitrary key-value pairs, e.g. a @litchar{:} key and type
|
|
pattern.
|
|
|
|
A context has @racket[let*] semantics where each binding is in scope for
|
|
subsequent parts of the context. This means type and term variables may be in
|
|
the same context (if properly ordered). In addition, Turnstile allows writing
|
|
two separate contexts, grouped by parens, where bindings in first are in scope
|
|
for the second. This is often convenient for scenarios that Racket's pattern
|
|
language cannot express, e.g., when there two distinct groups of bindings, a
|
|
pattern @racket[x ... y ...] will not work as expected.
|
|
|
|
For convenience, lone identifiers written to the left of the turnstile are
|
|
automatically treated as type variables.
|
|
|
|
@; ----------------------------------------------------------------------------
|
|
@bold{Conclusion}
|
|
|
|
Bidirectional judgements below the conclusion line invert their inputs and
|
|
outputs so that both positions in @racket[[⊢ e- ⇒ τ]] are syntax templates and
|
|
means that @racket[e-] is the output of the generated macro and it has type τ
|
|
attached. The generalized key-value form of the bidirectional judgements may
|
|
also be used in the conclusion.
|
|
|
|
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.}
|
|
|
|
]}
|
|
|
|
@; ----------------------------------------------------------------------------
|
|
@bold{Note}
|
|
|
|
@racket[define-typed-syntax] is generated by
|
|
@racket[define-syntax-category]. See @racket[define-syntax-category] for more
|
|
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.}
|
|
|
|
@; ~typecheck and ~⊢
|
|
|
|
@defform[(~typecheck premise ...)]{
|
|
A @racket[syntax-parse] @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander}
|
|
that supports typechecking syntax.
|
|
|
|
For example the pattern
|
|
|
|
@racketblock[
|
|
(~typecheck
|
|
[⊢ a ≫ a- ⇒ τ_a]
|
|
[⊢ b ≫ b- ⇐ τ_a])]
|
|
|
|
typechecks @racket[a] and @racket[b], expecting @racket[b] to have the
|
|
type of @racket[a], and binding @racket[a-] and @racket[b-] to the
|
|
expanded versions.
|
|
|
|
This is most useful in places where you want to do typechecking in
|
|
something other than a type rule, like in a function or a syntax
|
|
class.
|
|
|
|
@(let ([ev (make-base-eval)])
|
|
(ev '(require turnstile/turnstile))
|
|
@examples[
|
|
#:eval ev
|
|
(begin-for-syntax
|
|
(struct clause [stx- result-type])
|
|
(code:comment "f : Stx -> Clause")
|
|
(define (f stx)
|
|
(syntax-parse stx
|
|
[(~and [condition:expr body:expr]
|
|
(~typecheck
|
|
[⊢ condition ≫ condition- ⇐ Bool]
|
|
[⊢ body ≫ body- ⇒ τ_body]))
|
|
(clause #'[condition- body-] #'τ_body)])))
|
|
])}
|
|
|
|
@defform*[[(~⊢ tc ...)]]{
|
|
A shorthand @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander}
|
|
for @racket[(~typcheck [⊢ tc ...])].
|
|
|
|
For example the pattern @racket[(~⊢ a ≫ a- ⇒ τ_a)] typechecks
|
|
@racket[a], binding the expanded version to @racket[a-] and the type
|
|
to @racket[τ_a].
|
|
|
|
@(let ([ev (make-base-eval)])
|
|
(ev '(require turnstile/turnstile))
|
|
@examples[
|
|
#:eval ev
|
|
(begin-for-syntax
|
|
(struct clause [stx- result-type])
|
|
(code:comment "f : Stx -> Clause")
|
|
(define (f stx)
|
|
(syntax-parse stx
|
|
[(~and [condition:expr body:expr]
|
|
(~⊢ condition ≫ condition- ⇐ Bool)
|
|
(~⊢ body ≫ body- ⇒ τ_body))
|
|
(clause #'[condition- body-] #'τ_body)])))
|
|
])}
|
|
|
|
@; 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 : τ)
|
|
(define-primop typed-op-id #:as op-id τ)
|
|
(define-primop typed-op-id #:as 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)
|
|
(define-syntax-category key1 name-id)
|
|
(define-syntax-category key1 name-id key2))]{
|
|
|
|
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 forms and functions below.
|
|
|
|
Each category of syntax is associated with two keys: @racket[key1] is used when
|
|
attaching values in the category to other syntax, e.g., attaching types to
|
|
terms, and @racket[key2] is used for attaching an additional syntax property to
|
|
values in the category, e.g. using @racket[#%type] to indicate well-formedness.
|
|
|
|
If no keys are specified, @racket[key1] is @litchar{:} and @racket[key2] is
|
|
@litchar{::}. If only @racket[key1] is given, then @racket[key2] is
|
|
@racket[key1] appended to itself.
|
|
|
|
Defining another category, e.g., @racket[(define-syntax-category kind)],
|
|
defines a separate set of forms and functions, e.g.,
|
|
@racket[define-kinded-syntax], @racket[define-base-kind], @racket[kind=?], etc.
|
|
|
|
@; ----------------------------------------------------------------------------
|
|
@italic{The following forms and functions are automatically defined by a
|
|
@racket[(define-syntax-category type)] declaration:}
|
|
|
|
@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] @racket[current-type-eval] for
|
|
typechecking, and uses @litchar{:} as the key when an explicit key is not specificed in type judgements.}
|
|
|
|
@item{@defform*[((define-base-type base-type-name-id)
|
|
(define-base-type base-type-name-id key tag))]{
|
|
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[τ].}]}
|
|
|
|
Types defined with @racket[define-base-type] are automatically tagged with a
|
|
@racket[key2]-keyed (as specified in the @racket[define-syntax-category]
|
|
declaration) @racket[#%type] syntax property, unless second form above is used,
|
|
in which case the specified @tt{tag} is attached to the type using the
|
|
specified @tt{key}.}
|
|
|
|
@item{@defform[(define-base-types base-type-name-id ...)]{Defines multiple base types, each using the default key.}}
|
|
|
|
@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[τ] subsequently defines:
|
|
@itemlist[
|
|
|
|
@item{@racket[τ], a macro for constructing an instance of type @racket[τ],
|
|
with the specified arity. Validates inputs and expands to
|
|
@racket[τ-], attaching @racket[#%type] at @tt{key2}.}
|
|
|
|
@item{@racket[τ-], an internal macro that expands to the internal
|
|
(i.e., fully expanded) type representation. Does not validate inputs
|
|
or attach any extra properties. 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 communicate accessors to a pattern
|
|
matching form.}}
|
|
@item{
|
|
@defform[(define-internal-type-constructor name-id option ...)
|
|
#:grammar
|
|
([option (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 #:arr kindcon)
|
|
(code:line #:arg-variances expr)
|
|
(code:line #:extra-info stx)])]{
|
|
Analogous to @racket[define-internal-type-constructor], but for binding types.}}
|
|
@item{
|
|
@defform[(type-out ty-id ...)]{
|
|
A @racket[provide]-spec that, for each 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 attaching 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]].
|
|
Binds a @racket[x] attribute to the binding identifier, and a @racket[type] attribute
|
|
to the type's expanded representation.}}
|
|
@item{@defthing[type-ctx stx-class]{A syntax class recognizing
|
|
syntax objects with shape @racket[(b:type-bind ...)].
|
|
Binds a @racket[x] attribute to the binding identifiers, and a @racket[type] attribute
|
|
to the expanded representation of the types.}}
|
|
@item{@defthing[type-ann stx-class]{A syntax class recognizing
|
|
syntax objects with shape @racket[{τ:type}] where the braces are required.
|
|
Binds a @racket[norm] attribute to the type's expanded representation.}}
|
|
|
|
@item{@defproc[(assign-type [e syntax?] [τ syntax?]) syntax?]{
|
|
Phase 1 function that calls @racket[current-type-eval] on @racket[τ] and attaches it to @racket[e] using @tt{key1}.}}
|
|
|
|
@item{@defproc[(typeof [e expr-stx]) type-stx]{
|
|
Phase 1 function returning type of @racket[e], at @tt{key1}.}}
|
|
|
|
]
|
|
}
|
|
|
|
@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,
|
|
particularly for commonly used macros like @racket[#%app].
|
|
|
|
@; Sec: turnstile/lang ----------------------------------------------
|
|
@section[#:tag "turnstilelang"]{@hash-lang[] @racketmodname[turnstile]/lang}
|
|
|
|
Languages implemented using @hash-lang[] @racketmodname[turnstile]
|
|
must manually 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[(infer [es (listof expr-stx)]
|
|
[#:ctx ctx (listof bindings) null]
|
|
[#:tvctx tvctx (listof tyvar-bindings) null]
|
|
[#:tag tag symbol? ':])
|
|
(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]))]
|
|
|
|
might return
|
|
|
|
@racket[(list #'() #'(x-) #'((#%plain-app x- 1)) #'(Int))].
|
|
|
|
The context elements have the same shape as in @racket[define-typed-syntax].
|
|
The contexts use @racket[let*] semantics, where each binding is in scope for
|
|
subsequent bindings.
|
|
|
|
Use the @tt{tag} keyword argument to specify the key for the
|
|
returned "type". The default key is @litchar{:}. For example, a programmer may
|
|
want to specify a @litchar{::} key when using @racket[infer] to compute the
|
|
kinds on types.}
|
|
|
|
@defproc[(subst [τ type-stx]
|
|
[x identifier?]
|
|
[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 identifier?)]
|
|
[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?]) exact-nonnegative-integer?]{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].}
|