PLAI Typed Language
#lang plai-typed | package: plai-typed |
The plai-typed language is a statically typed language that resembles the plai language, though with a smaller set of syntactic forms and built-in functions.
The body of a plai-typed module is a sequence of definitions and expressions. Like the plai languages the module exports all top-level definitions. When a plai-typed module is imported into a module that does not use plai-typed, the imports have contracts (matching reflecting the exported bindings’ types).
1 Definitions
In a define-type declaration, the contract/predicate position for variant fields changes to a colon followed by a type. In addition, define and lambda forms support type annotations on arguments and for results. The syntax is otherwise merely restricted from the normal PLAI language.
syntax
(define id expr)
(define id : type expr) (define (id id/type ...) expr) (define (id id/type ...) : type expr)
id/type = id | [id : type]
syntax
(define-values (id/type ...) expr)
id/type = id | [id : type]
syntax
(define-type tyid/abs [variant-id (field-id : type)] ...)
tyid/abs = id | (id 'arg-id ...)
syntax
(define-type-alias tyid/abs type)
tyid/abs = id | (id 'arg-id ...)
syntax
(require spec ...)
spec = module-path | (typed-in module-path [id : type] ...) | (opaque-type-in module-path [type-id predicate-id] ...) | (rename-in spec [orig-id new-id] ...)
When a module-path is not wrapped with typed-in or opaque-type-in, then module-path must refer to a module that is implemented with plai-typed.
When module-path is wrapped with typed-in, then only the specified ids are imported from module-path, and the type system assumes (without static or additional dynamic checks) the given type for each id.
When module-path is wrapped with opaque-type-in, then the corresponding type-ids are bound as opaque datatypes, where predicate-id from module-path is a run-time predicate (used for contracts as needed for cooperation with untyped code) for instances of the datatype.
syntax
(trace id ...)
syntax
(module id module-path form ...)
(module sub plai-typed (define n 8)) (require 'sub) (+ n 1)
syntax
(module+ id form ...)
(module+ test (test 11 (add-one 10))) (define (add-one n) (+ 1 n))
syntax
(include path-spec)
syntax
(define-syntax-rule (id pattern ...) template)
syntax
(define-syntax id macro-expr)
(define-syntax (id arg-id) macro-body ...)
macro = (syntax-rules ....) | (lambda ....)
A macro of the form
(define-syntax-rule (id pattern ...) template)
is equivalent to
(define-syntax id (syntax-rules () [(id pattern ...) template]))
syntax
(splice form ...)
2 Expressions
An expression can be a literal constant that is a number (type number), a string (type string), a symbol (type symbol) written with quote or ', an S-expression (type s-expression) also written with quote or ', #t (type boolean), #f (type boolean), or a character (type char). An expression also can be a bound identifier (in which case its type comes from its binding).
syntax
(quote s-exp)
s-exp = id | number | string | boolean | (s-exp ...)
syntax
(quasiquote qq-form)
qq-form = id | number | string | boolean | (qq-form ...) | (unquote expr) | (unquote-splicing expr) | (quasiquote expr)
syntax
syntax
syntax
(#%app expr expr ...)
syntax
syntax
(if test-expr expr expr)
syntax
(cond [test-expr expr] ...)
(cond [test-expr expr] ... [else expr])
syntax
(case expr [(id-or-number ...) expr] ...)
(case expr [(id-or-number ...) expr] ... [else expr])
The dispatching mode, symbol or number, is inferred from the id-or-numbers, which must all be symbols or numbers for a given use of case. If no clause provides a number or symbol, then symbol dispatch is inferred.
syntax
(begin expr ...+)
syntax
(local [definition ...] expr)
syntax
(letrec ([id expr] ...) expr)
syntax
(let ([id expr] ...) expr)
syntax
(let* ([id expr] ...) expr)
syntax
(shared ([id expr] ...) expr)
syntax
(parameterize ([param-expr val-expr] ...) expr)
syntax
(set! id expr)
syntax
(list elem ...)
syntax
(vector elem ...)
syntax
(values elem ...)
The type of each elem is independent.
syntax
(type-case tyid/abs val-expr [variant-id (field-id ...) expr] ...)
(type-case tyid/abs val-expr [variant-id (field-id ...) expr] ... [else expr])
tyid/abs = id | (id type ...)
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
map2 : (('a 'b -> 'c) (listof 'a) (listof 'b) -> (listof 'c))
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
string->symbol : (string -> symbol)
value
symbol->string : (symbol -> string)
value
string-ref : (string number -> char)
value
string-length : (string -> number)
value
value
value
string->list : (string -> (listof char))
value
list->string : ((listof char) -> string)
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
The s-exp-symbol? function determines whether an S-expression is a symbol; in that case, s-exp->symbol acts the identity function to produce the symbol, otherwise an exception is raised. The symbol->s-exp function similarly acts as the identity function to view a symbol as an S-expression.
The other functions work similarly for numbers, strings, booleans, and lists of S-expressions.
value
read : (-> s-expression)
value
make-vector : (number 'a -> (vectorof 'a))
value
vector-ref : ((vectorof 'a) number -> 'a)
value
vector-set! : ((vectorof 'a) number 'a -> void)
value
vector-length : ((vectorof 'a) -> number)
value
make-parameter : ('a -> (parameterof 'a))
value
parameter-ref : ((parameterof 'a) -> 'a)
value
parameter-set! : ((parameterof 'a) 'a -> void)
value
value
value
value
value
hash-remove! : ((hashof 'a 'b) 'a -> void)
value
value
hash-remove : ((hashof 'a 'b) 'a -> (hashof 'a 'b))
value
value
value
value
value
value
See also module+.
syntax
(time expr)
3 Types
The void identifier also works as an expression of type (-> void).
syntax
(type ... -> type)
syntax
(type * ...+)
syntax
()
syntax
(listof type)
syntax
(boxof type)
syntax
(vectorof type)
syntax
(parameterof type)
syntax
(hashof type type)
syntax
(optionof type)
(define-type (optionof 'a) [none] [some (v : 'a)])
4 Syntactic Literals
syntax
syntax
syntax
5 Type Checking and Inference
Type checking and inference is just as in ML (Hindley-Milner), with a few small exceptions:
Functions can take multiple arguments, instead of requring a tuple of arguments. Thus, (number number -> number) is a different type than either ((number * number) -> number), which is the tuple variant, or (number -> (number -> number)), which is the curried variant.
Since all top-level definitions are in the same mutually-recursive scope, the type of a definition’s right-hand side is not directly unified with references to the defined identifier on the right-hand side. Instead, every reference to an identifier—
even a reference in the identifier’s definition— is unified with a instantiation of a polymorphic type inferred for the definition. Compare OCaml:
# let rec f = fun x -> x
and h = fun y -> f 0
and g = fun z -> f "x";;
This expression has type string but is here used with type int
with
(define (f x) x)
(define (h y) (f 0))
(define (g y) (f "x"))
; f : ('a -> 'a)
; h : ('a -> number)
; g : ('a -> string)
A minor consequence is that polymorphic recursion (i.e., a self call with an argument whose type is different than that for the current call) is allowed. Recursive types, however, are prohibited.
The usual value restriction applies for inferring polymorphic types, where expression matching the following grammar (before macro expansion, unfortunately) are considered values:
value-expr = (lambda (id/ty ...) expr) | (lambda (id/ty ...) : type expr) | (values value-expr ...) | (list value-expr ...) | empty | (cons value-expr value-expr) | (hash value-expr ...) | (variant-id value ...) | 'datum | id | string | character | number | boolean where variant-id is none, some, or a constructor bound by define-type.
Variables are mutable when set! is used, but assignment via set! is disallowed on a variable after a polymorphic type has been inferred for it (e.g., in an interaction after type checking is complete).
Since all definitions are recursively bound, and since the right-hand side of a definition does not have to be a function, its possible to refer to a variable before it is defined. The type system does not prevent “reference to identifier before definition” errors.
Type variables are always scoped locally within a type expression.
Compare OCaml:
# function (x : 'a), (y : 'a) -> x;;
- : 'a * 'a -> 'a = <fun>
with
> (lambda ((x : 'a) (y : 'a)) x)
- ('a 'b -> 'a)
> (define f : ('a 'a -> 'a) (lambda (x y) x))
> f
- ('a 'a -> 'a)
When typechecking fails, the error messages reports and highlights (in pink) all of the expressions whose type contributed to the failure. That’s often too much information. As usual, explicit type annotations can help focus the error message.
6 Untyped with Typed Syntax
#lang plai-typed/untyped | package: plai-typed |
The define-syntax, define-syntax-rule, module+, and require forms from plai-typed/untyped are the bindings from racket/base instead of plai-typed.
syntax
syntax