The previous section introduced the basics of the Typed Racket type system. In this section, we will see several new features of the language, allowing types to be specified and used.
In general, variables in Typed Racket must be annotated with their type. A later subsection (When do you need type annotations?) introduces a heuristic which more precisely details when type annotations are needed.
We have already seen the : type annotation form. This is useful for definitions, at both the top level of a module
and in an internal definition
In addition to the : form, almost all binding forms from racket have counterparts which allow the specification of types. The define: form allows the definition of variables in both top-level and internal contexts.
The let: form is exactly like let, but type annotations are provided for each variable bound. Here, x is given the type Number. The let*: and letrec: are similar. Annotations are optional with let: and variants.
(let-values: ([([x : Number] [y : String]) (values 7 "hello")]) (+ x (string-length y)))
The let*-values: and letrec-values: forms are similar.
(case-lambda: [() 0] [([x : Number]) x])
When a single variable binding needs annotation, the annotation can be applied to a single variable using a reader extension:
It is also possible to provide an expected type for a particular expression.
> (ann "not a number" Number)
eval:2:0: Type Checker: type mismatch
In this example, x has the type Exact-Positive-Integer.
Similarly, top-level constant definitions do not require annotation:
(define y "foo")
In this examples, y has the type String.
Finally, the parameter types for loops are inferred from their initial values.
The last several subsections explained several ways to add type annotations and explained that type inference allows some annotations to be left out. Since annotations can often be omitted, it is helpful to know the situations in which they are actually required.
The following four rules of thumb will usually suffice to determine if a type annotation is necessary.
An expression or definition needs a type annotation if it:
is a define form for a function,
is a lambda that is immediately bound to a variable,
is a lambda that is an argument to a polymorphic function, or
is defining a mutable variable.
Here are examples that correspond to each of the cases above:
In all four cases, if the type annotation is omitted then the inferred type will often be too conservative (e.g., Any) and the code may not type-check.
Any type can be given a name with define-type.