Plait Language
#lang plait | package: plait |
The Plait language syntactically resembles the plai language, which is based on racket, but the type system is close to that of ML. For a quick introduction, see the tutorial section or the tutorial videos.
1 Tutorial
The Plait tutorial videos provide most of the same information as this section.
For a quick refresher of the tutorial content, try "demo.rkt".
1.1 Getting Started
To get started with Plait, download Racket, install it, start DrRacket, and install the plait package using DrRacket’s Install Package... menu item in the File menu.
Then, in the top part of the DrRacket window, type
#lang plait
and click the Run button (or use the keyboard shortcut shown in the Racket menu). In the bottom part of the window, type 1 and hit Return, and you should see this result:
> 1 - Number
1
In other words, the expression 1 has type Number, and the value of the expression is 1.
In this tutorial, we will mostly show expressions as if typed in that bottom area. You can also put the expressions in the top area and hit Run again, but in that case, the type of the result will not print before the result.
In a few places, the tutorial shows lines that include a semicolon, ;. A semicolon starts a comment that continues to the end of the line. For examples of other comment forms, see "demo.rkt".
1.2 Simple Data
Plait supports various kinds of numbers, all with type Number:
> 1 - Number
1
> 0.5 - Number
0.5
> 1/2 - Number
1/2
> 1+2i - Number
1+2i
The number 1/2 is a literal number, not a division operation. Similarly, 1+2i is a literal complex number. The full syntax of numbers is probably not important, but it’s Racket’s number syntax.
The booleans true and false are written #t and #f, but you can also write them #true and #false:
> #t - Boolean
#t
> #f - Boolean
#f
> #true - Boolean
#t
> #false - Boolean
#f
Strings are written the usual way with a starting and ending ":
> "apple" - String
"apple"
> "banana cream pie" - String
"banana cream pie"
> "yes, \"escape\" quotes with backslash" - String
"yes, \"escape\" quotes with backslash"
In addition to strings, Plait includes string-like values called symbols. A symbol is written with a single quote ' followed by a sequence of non-whitespace characters.
> 'apple - Symbol
'apple
> 'banana-cream-pie - Symbol
'banana-cream-pie
> 'a->b - Symbol
'a->b
> '#%$^@*&?! - Symbol
'#%$^@*&?!
Almost any non-whitespace character is allowed in a symbol, except for the following characters:
( ) [ ] { } " , ' ` ;
Characters like -, >, and ? are not only allowed in symbols, they are frequently used that way. The | and \ characters are allowed, but they’re treated as quoting and escaping characters, so don’t use them.
Individual characters are infrequently used in Plait, but they’re written with #\:
> #\a - Char
#\a
> #\b - Char
#\b
> #\A - Char
#\A
> #\space ; same as #\ followed by a space - Char
#\space
1.3 Using Built-in Functions
Plait includes some of the usual functions on numbers, like floor and max. To call a function, start with an open parenthesis, then use the function name, then the argument, and finally a closing parenthesis:
> (floor 1.2) - Number
1.0
> (max 3 5) - Number
5
The parenthesis must be before the function, not after. Don’t use commas between arguments. Also, extra parentheses are not allowed. If you include extra parentheses around 3, for example, then Plait will complain that 3 is not a function, since the parentheses mean a function call:
> (max (3) 5) eval:24:0: typecheck failed: call of a non-function
possible reason: extra parentheses create a function call
type mismatch: (-> '_a) vs. Number
sources:
3
The same error happens if you add parentheses around the call to max, since the result of max is a number:
> ((max 3 5)) eval:25:0: typecheck failed: call of a non-function
possible reason: extra parentheses create a function call
type mismatch: (-> '_a) vs. Number
sources:
(max 3 5)
max
The type of a function is written with -> in parentheses. The function’s argument types appear before the arrow, and the function’s result type is after the arrow. A function is a value, so if you evaluate just max without calling it, then Plait will show the type and print that the result is a procedure (which is synonymous with “function” in Plait):
> max - (Number Number -> Number)
#<procedure:max>
Unlike most languages, arithmetic operations such as + and
* in Plait are just functions, and they are called the same
way as other functions—
> (+ 3 5) - Number
8
> (* 3 5) - Number
15
> + - (Number Number -> Number)
#<procedure:+>
Note that + is allowed as a function name in fundamentally the same way that + is allowed in the symbol '+.
If you try to put the operator-as-function in the middle of its arguments, Plait will complain that the first argument isn’t a function, because the opening parenthesis means that first thing in the parenthesis should be a function to call:
> (3 + 5) eval:30:0: typecheck failed: call of a non-function
possible reason: extra parentheses create a function call
type mismatch: ((Number Number -> Number) Number -> '_a)
vs. Number
sources:
3
If you omit then parentheses, then Plait see three separate expressions:
> + 3 5 - (Number Number -> Number) #<procedure:+> - Number 3 - Number 5
The style of syntax that puts a function/operation name up front and grouped with its arguments in parentheses is called parenthesized prefix notation.
Treating + like any other function makes Plait simpler, as does using parenthesized prefix notation. Since you didn’t have to create Plait, you may not care that Plait is simpler this way. But if you’re building your own interpreter in a class that’s about programming languages, then Plait’s regularity turns out to be a convenient design to imitate; you can spend more time studying the meaning of programming constructs and less time worrying about the syntax of those constructs.
Here are some example uses of other built-in functions, and you can click on any of the function names her eto jump to the documentation:
> (not #t) - Boolean
#f
> (not #f) - Boolean
#t
> (+ 1 2) - Number
3
> (- 1 2) - Number
-1
> (* 1 2) - Number
2
> (< 1 2) - Boolean
#t
> (> 1 2) - Boolean
#f
> (= 1 2) - Boolean
#f
> (<= 1 2) - Boolean
#t
> (>= 1 2) - Boolean
#f
> (string-append "a" "b") - String
"ab"
> (string=? "a" "b") - Boolean
#f
> (string-ref "a" 0) - Char
#\a
> (string=? "apple" (string-append "a" "pple")) - Boolean
#t
> (equal? "apple" (string-append "a" "pple")) - Boolean
#t
> (eq? 'apple 'apple) - Boolean
#t
> (eq? 'apple 'orange) - Boolean
#f
Note that some operations work on multiple types. For example, equal? works on any two values, as long as the two values have the same type. That flexibility and constraint is reflected in the type of equal? by a symbol placeholder 'a, which you can read as “a type to be picked later.” A specific type is picked for every individual use of equal?:
> (equal? 1 1) - Boolean
#t
> (equal? "one" "one") - Boolean
#t
> equal? - ('a 'a -> Boolean)
#<procedure:equal?>
> (equal? 1 "one") eval:51:0: typecheck failed: Number vs. String
sources:
equal?
1
"one"
1.4 Conditionals
The if form works in the usual way, and it follows the parenthesized-prefix convention of being grouped with its subexpressions with parentheses:
> (if (equal? "apple" "banana") 'yes 'no) - Symbol
'no
The line breaks above don’t matter to Plait, but readers of your programs will appreciate it if you normally put the “then” and “else” branches on their own lines and correctly intent them. The correct indentation is the indentation that DrRacket gives you automatically when you hit Return after (equal? "apple" "banana"). If you ever need to reindent a region of code, you can select the region and hit Tab.
The cond form is a multi-way if. A cond form has a sequence of clauses, where each clause has a “question” and a result expression. The result expression is used only when the question produces true. The cond form tries the clauses in order, and as soon as it finds a true result from a question, it produces the corresponding result. The last clause’s question cal be else as a synonym for #t.
> (cond [(< 2 1) 17] [(> 2 1) 18]) - Number
18
> (cond [(< 2 1) (/ 1 0)] ; result expression skipped [(> 2 1) 18]) - Number
18
> (cond [#t 8] [#t (/ 1 0)]) ; second clause not reached - Number
8
> (cond [(< 3 1) 0] [(< 3 2) 1] [(< 3 3) 2] [(< 3 4) 3]) - Number
3
> (cond [(eq? 'a 'b) 0] [(eq? 'a 'c) 1] [else 2]) - Number
2
Plait doesn’t distinguish between square brackets [ and
] and parentheses ( and ), as long as
each opener and closer match. You could use parentheses instead of
square brackets in the above examples—
The and and or forms are short-cicuiting, too, and they work with any number of boolean subexpressions:
> (and #t #t) - Boolean
#t
> (and #t #f) - Boolean
#f
> (and (< 2 1) #t) - Boolean
#f
> (and (< 2 1) (zero? (/ 1 0))) ; second expression is not evaluated - Boolean
#f
> (or #f #t) - Boolean
#t
> (or #f #f) - Boolean
#f
> (or (< 1 2) (zero? (/ 1 0))) ; second expression is not evaluated - Boolean
#t
> (and #t #t #t #t) - Boolean
#t
> (or #f #f #f) - Boolean
#f
1.5 Lists
Plait lists are uniform, meaning that all of the elements of a list must have the same type. The list form creates a list:
> (list 1 2 (+ 3 4)) - (Listof Number)
'(1 2 7)
> (list (string-append "a" "b") "c") - (Listof String)
'("ab" "c")
As you can see, the type of a list is written with Listof and then the type of the elements of the list. You also see that the result is printed using '. You can use a ' to create a list, but only for literal-value content (i.e., no subexpressions to evaluate):
> '(1 2 7) - (Listof Number)
'(1 2 7)
> '(1 2 (+ 3 4)) eval:70:0: typecheck failed: Symbol vs. Number
sources:
(+ 3 4)
3
+
To understand that last error message, start by observing that the ' for a literal list is the same as a the ' for a symbol. As it turns out, a ' for a list implicitly distributes the ' to each element of the list. So, '(a b) is equivalent to (list 'a 'b). It’s also the case that '(1 2 7) is equivalent to (list '1 '2 '7), because ' has no effect on a number, boolean, or string:
> '1 - Number
1
> '#t - Boolean
#t
> '"apple" - String
"apple"
> '(milk cookies) - (Listof Symbol)
'(milk cookies)
> '((pen paper) (rock scissors paper)) - (Listof (Listof Symbol))
'((pen paper) (rock scissors paper))
The expression '(1 2 (+ 3 4)) fails because that’s the same as (list 1 2 '(+ 3 4)), and '(+ 3 4) fails because it’s the same as (list '+ 3 4), but a list cannot mix a symbol with numbers.
A list is immutable. That is, the value '(1 2 3) is as
unchanging as the numbers 1, 2, and 3
within the list. You can’t change a list to add new elements to
it—
> (cons 1 '(2 3)) - (Listof Number)
'(1 2 3)
> (cons "apple" '("banana")) - (Listof String)
'("apple" "banana")
The cons operation is constant-time, because a list is internally represented as a singly linked list, and cons simply creates a new cell that contains the new value and then points to the existing list.
If you have two lists, instead of one element and a list, you can combine the lists with append:
> (append '(1 2) '(3 4)) - (Listof Number)
'(1 2 3 4)
Don’t confuse cons and append. The cons function takes an element and a list, while append takes a list and a list. That difference is reflected in their types:
> cons - ('a (Listof 'a) -> (Listof 'a))
#<procedure:cons>
> append - ((Listof 'a) (Listof 'a) -> (Listof 'a))
#<procedure:append>
Mixing them up will trigger a type error:
> (cons '(1) '(2 3)) eval:81:0: typecheck failed: (Listof Number) vs. Number
sources:
cons
2
(quote (2 3))
(quote (1))
> (append 1 '(2 3)) eval:82:0: typecheck failed: (Listof '_a) vs. Number
sources:
append
1
A list doesn’t have to contain any values:
> (list) - (Listof '_a)
'()
The empty list is so useful that it has a name: empty. Although the list form may seem fundamental, the true list-construction primitives are empty and cons, and you can build up any other list using those:
> empty - (Listof 'a)
'()
> (cons "food" empty) - (Listof String)
'("food")
> (cons "dog" (cons "food" empty)) - (Listof String)
'("dog" "food")
The empty? function determines whether a list is empty, and cons? determines whether a list has at least one item:
> (empty? empty) - Boolean
#t
> (empty? '()) - Boolean
#t
> (cons? (cons 1 '())) - Boolean
#t
> (cons? '(1)) - Boolean
#t
> (cons? empty) - Boolean
#f
> (empty? '(1)) - Boolean
#f
The cons operation constructs a new value from two pieces. The first and rest operations are the opposite of cons. Given a value produced by cons, first returns the item that cons added to the start of the list, and rest returns the list that cons added to. More generally, first gets the first item from a list, and rest gets everything list in the list when the first argument is removed.
> (first (cons 1 '(2 3))) - Number
1
> (rest (cons 1 '(2 3))) - (Listof Number)
'(2 3)
> (first '("apple" "banana" "coconut")) - String
"apple"
> (rest '("apple" "banana" "coconut")) - (Listof String)
'("banana" "coconut")
> (first (rest '("apple" "banana" "coconut"))) - String
"banana"
> (rest (rest '("apple" "banana" "coconut"))) - (Listof String)
'("coconut")
Plait also provides second, third, fourth, and list-ref. Those functions are sometimes useful to extract pieces of a list that has a known shape. Functions that take the first of a list and recur with the rest turn out to be be more common. Here’s a function that check whether "milk" is in a list of strings:
> (define (got-milk? [items : (Listof String)]) (cond [(empty? items) #f] [(cons? items) (or (string=? (first items) "milk") (got-milk? (rest items)))])) > (got-milk? empty) - Boolean
#f
> (got-milk? '("milk" "cookies")) - Boolean
#t
> (got-milk? '("cookies" "milk")) - Boolean
#t
> (got-milk? '("cookies" "cream")) - Boolean
#f
1.6 Definitions
The define form defines an identifier to be a synonym for a value:
> (define pi 3.14) > pi - Number
3.14
> (define tau (+ pi pi)) > tau - Number
6.28
The define form can also define a function. The difference is that define for a function definition is followed by an open parenthesis, then the function name, a name for each argument, and a closing parenthesis. The expression afterward is the body of the function, which can refer to the function arguments and is evaluated when the function is called.
> (define (circle-area r) (* pi (* r r))) > (circle-area 10) - Number
314.0
Since Getting Started, we have been evaluating forms only
in DrRacket’s bottom area, which is also known as the
interactions area. Definitions normally go in the top
area—
Put these two definitions in the definitions area:
(define (is-odd? x) (if (zero? x) #f (is-even? (- x 1))))
(define (is-even? x) (if (zero? x) #t (is-odd? (- x 1))))
Click Run. The functions is-odd? and is-even? are now available in the interactions area:
> is-odd? - (Number -> Boolean)
#<procedure:is-odd?>
> (is-odd? 12) - Boolean
#f
In our definitions of pi and tau, plait inferred that the newly defined names have type Number and that is-odd? has type (Number -> Boolean). Programs are often easier to read and understand if you write explicitly the type that would otherwise be inferred. Declaring types can sometimes help improve or localize error messages when Plait’s attempt to infer a type fails, since inference can other end up depending on the whole program.
Declare a type for a constant by writing : followed by a type after the defined identifier:
(define groceries : (Listof String) '("milk" "cookies"))
Alternatively, you can declare an idenitifier’s type separate from its definition by using :.
(groceries : (Listof String)) (define groceries '("milk" "cookies"))
The declaration can appear before or after the definition, as long as it is in the same layer of declarations as the definition. You can even have multiple type definitions for the same identifier, and the type checker will ensure that they’re all consistent.
For a function, attach a type to an argument by writing square brackets around the argument name, :, and a type. Write the function’s result type with : and the type after the parentheses that group the function name with its arguments.
(define (starts-milk? [items : (Listof String)]) : Boolean (equal? (first items) "milk"))
Or, of course, declare the type separately:
(starts-milk? : ((Listof String) -> Boolean))
(define (starts-milk? items) (equal? (first items) "milk"))
You can declare local functions and constants by using the local form as a wrapper. The definitions that appear after local are visible only within the local form, and the result of the local form is the value of the expression that appears after the definitions. The definitions must be grouped with square brackets.
> (local [(define pi-ish 3) (define (approx-circle-area r) (* pi-ish (* r r)))] (approx-circle-area 2)) - Number
12
> pi-ish ; not visible outside the local eval:123:0: code:line: free variable while typechecking
in: code:line
> approx-circle-area ; not visible outside the local eval:124:0: code:line: free variable while typechecking
in: code:line
The local form is most often used inside a function to define a helper function or to avoid a repeated computating involving the function arguments.
> (define (discard-first-if-fruit items) (local [(define a (first items))] (cond [(equal? a "apple") (rest items)] [(equal? a "banana") (rest items)] [else items]))) > (discard-first-if-fruit '("apple" "potato")) - (Listof String)
'("potato")
> (discard-first-if-fruit '("banana" "potato")) - (Listof String)
'("potato")
> (discard-first-if-fruit '("potato" "apple")) - (Listof String)
'("potato" "apple")
The let and letrec forms are similar to local, but they are somewhat more compact by avoiding the requirement to write define. The discard-first-if-fruit example above can be equivalently written using let:
(define (discard-first-if-fruit items) (let ([a (first items)]) (cond [(equal? a "apple") (rest items)] [(equal? a "banana") (rest items)] [else items])))
1.7 Datatypes
So far, we have only seen built-in types like Number and (Listof String). Sometimes, it’s useful to define your own name as a shorthand for a type, such as defining Groceries to be equivalent to (Listof String):
(define-type-alias Groceries (Listof String)) (define shopping-list : Groceries '("milk" "cookies"))
Note that, by convention, all type names are capitalized. Plait is case-sensitive.
But what if the data that you need to represent is not easily encoded in existing types, such as when you need to keep track of a tiger’s color and stripe count (which doesn’t work as a list, since a list can’t have a number and a string)? And what if your type, say Animal, has values of different shapes: tigers that have color and stripe counts, plus snakes that have a color, weight, and favorite food?
The define-type form handles those generalizations. The general form is
(define-type Type (variant-name_1 [field-name_1 : Type_1] [field-name_2 : Type_2] ...) (variant-name_2 [field-name_3 : Type_3] [field-name_4 : Type_4] ...) ...)
with any number of variants and where each variant has any number of typed fields. If you’re used to Java-style classes, you can think of Type as an interface, and each variant is a class that implements the interface. Unlike Java classes, a variant name doesn’t work as a type name; it only works to create an instance of the variant.
For example, the following definition is suitable for representing animals that can be either tigers or snakes:
(define-type Animal (tiger [color : Symbol] [stripe-count : Number]) (snake [color : Symbol] [weight : Number] [food : String]))
After this definition, Animal can be used as a type, while tiger and snake work as functions to create Animals:
> (tiger 'orange 12) - Animal
(tiger 'orange 12)
> (snake 'green 10 "rats") - Animal
(snake 'green 10 "rats")
The definition of Animal creates several additional functions:
tiger?, which takes an Animal and determines whether it was created by tiger (as opposed to snake);
snake?, which takes an Animal and determines whether it was created by snake (as opposed to tiger);
tiger-color and tiger-stripe-count, which take a tiger Animal and extract its color and stripe count, respectively; and
snake-color, snake-weight, and snake-food, which take a snake Animal and extract its color, weight, and favorite food, respectively.
The name tiger? was formed by adding a ? to the end of the variant name tiger, tiger-color is formed by adding a - between the variant name and field name, and so on.
> (define tony (tiger 'orange 12)) > (define slimey (snake 'green 10 "rats")) > (tiger? tony) - Boolean
#t
> (tiger? slimey) - Boolean
#f
> (tiger-color tony) - Symbol
'orange
> (snake-food slimey) - String
"rats"
> (tiger-color slimey) - Symbol
tiger-color: contract violation
expected: tiger?
given: (snake 'green 10 "rats")
in: the 1st argument of
(->
tiger?
(or/c
undefined?
...pkgs/plait/main.rkt:1013:41))
contract from: tiger-color
blaming: use
(assuming the contract is correct)
at: eval:132:0
Note that the type of (tiger-color slimey) printed before an error was reported. That’s because (tiger-color slimey) is well-typed as far as Plait can tell, since tiger-color wants an Animal and slimey has type Animal. We’ll see that type-case provides an alterntive to selectors like tiger-color that is less dangerous than the selector.
Using Animal as a type and the tiger? and snake? predicates, we can write a function that extracts the color of any animal:
> (define (animal-color [a : Animal]) : Symbol (cond [(tiger? a) (tiger-color a)] [(snake? a) (snake-color a)])) > (animal-color tony) - Symbol
'orange
> (animal-color slimey) - Symbol
'green
When writing animal-color, what if we forget the snake? case? What if we get snake-color and tiger-color backwards? Unfortunately, the type checker cannot help us detect those problems. If we use type-case, however, the type checker can help more.
The general form of a type-case expresison is
(type-case Type value-expression [(variant-name_1 field-var_1 field-var_2 ...) result-expression_1] [(variant-name_2 field-var_3 field-var_4 ...) result-expression_2] ...)
The value-expression must produce a value matching Type. Every variant of Type must be represented by a clause with a matching variant-name. For that clause, the number of field-vars must match the declared number of fields for the variant. The type checker can check all of those requirements.
To produce a value, type-case determines the variant that is instanited by the result of value-expression. For the clause matching that variant (by name), type-case makes each field-var stand for the corresponding field (by position) within the value, and then evaluates the corresponding result-expression. Here’s animal-color rewritten with type-case:
> (define (animal-color [a : Animal]) : Symbol (type-case Animal a [(tiger col sc) col] [(snake col wgt f) col])) > (animal-color tony) - Symbol
'orange
> (animal-color slimey) - Symbol
'green
Put the definitions of Anmal and animal-color in DrRacket’s definitions area. Then, you can mouse over a in animal-color to confirm that it means the a that is passed as an argument. Mouse over col to see that it means one of the variant-specific fields. Try changing the body of animal-color to leave out a clause or a field variable and see what error is reported when you hit Run.
You should think of type-case as a pattern-matching form. It matches a value like (tiger 'orange 12) to the pattern (tiger col sc) so that col stands for 'orange and sc stands for 12. A value like (snake 'green 10 "rats") does not match the pattern (tiger col sc), but it matches the pattern (snake col wgt f).
At the end of Lists, we saw a got-milk? function that uses cond, similar to the way the dangerous version of animal-color uses cond. The type-case form works on list types with empty and (cons fst rst) patterns, so here’s an improved got-milk?:
> (define (got-milk? [items : (Listof String)]) (type-case (Listof String) items [empty #f] [(cons item rst-items) (or (string=? item "milk") (got-milk? rst-items))])) > (got-milk? empty) - Boolean
#f
> (got-milk? '("cookies" "milk")) - Boolean
#t
Note that there are no parentheses around empty in
got-milk?. That’s because empty is never called as a
constructor function—
> (define-type Grade (letter [alpha : Symbol]) (pass-fail [pass? : Boolean]) (incomplete)) > (letter 'A) - Grade
(letter 'A)
> (pass-fail #t) - Grade
(pass-fail #t)
> (incomplete) - Grade
(incomplete)
> (define (passed-course? [g : Grade]) : Boolean (type-case Grade g [(letter a) (not (eq? a 'F))] [(pass-fail p?) p?] [(incomplete) #f])) > (passed-course? (letter 'B)) - Boolean
#t
> (passed-course? (incomplete)) - Boolean
#f
You can also use else for a final clause in type-case to catch any variants that are not already covered.
> (define (high-pass? [g : Grade]) : Boolean (type-case Grade g [(letter a) (eq? a 'A)] [else #f])) > (high-pass? (letter 'A)) - Boolean
#t
> (high-pass? (incomplete)) - Boolean
#f
When you use else, however, the type checker is less helpful for making sure that you’ve considered all cases.
1.8 Testing and Debugging
Plait includes built-in support for testing your programs. The test form takes two expressions and makes sure that they produce the same value. Typically, the first expression is a function call, and the second expression is the expected result of the function. The test form prints output that starts “good” if the test passes or “bad” if it fails.
> (define (taste s) (cond [(equal? s "milk") 'good] [else 'not-as-good])) > (test (taste "milk") 'good)
- Void
good (taste "milk") at line 162
expected: 'good
given: 'good
> (test (taste "brussel sprouts") 'not-as-good)
- Void
good (taste "brussel sprouts") at line 163
expected: 'not-as-good
given: 'not-as-good
> (test (taste "beets") 'bad) - Void
bad (taste "beets") at line 164
expected: 'bad
given: 'not-as-good
They say that no news is good news. To suppress the output for passing tests, so that only failing test strigger output, use (print-only-errors #t).
> (print-only-errors #t) - Void
> (test (taste "kale") 'not-as-good) - Void
> (test (taste "anchovies") 'bad) - Void
bad (taste "anchovies") at line 167
expected: 'bad
given: 'not-as-good
To test that an expression reports an expected error, use test/exn. The test/exn form’s section expression should produce a string, and test/exn checks that an error is reported where the string occurs in the error message. You can only test for errors that your program specifically reports using the error function.
> (define (always-fail [n : Number]) : Number (error 'always-fail "we're not actually returning a number")) > (test/exn (always-fail 42) "not actually") - Void
> (test/exn (always-fail 42) "should not get called") - Void
bad (always-fail 42) at line 170
expected: "should not get called"
given: "always-fail: we're not actually returning a number"
When you write a program (in the definitions area of DrRacket), the order of function definitions generally does not matter, even if the functions call each other. A test at the top level of a program, however, must appear after all functions that the test may end up calling. To relax this constraint, wrap tests in a (module+ test ....) form. A (module+ test ....) wrapper effectively moves its content to the end of the program.
(module+ test (test (retaste "milk") '(still good))) (define (retaste s) (list 'still (taste s)))
A good set of tests will cause all expressions in a program to be evaluated at least once. DrRacket can help you check that your program has good test coverage. In DrRacket’s Language menu, select Choose Language, click Show Details, click Submodules to run, and then select the Syntactic test suite coverage option. After selecting that option, when you Run a program, it will stay its normal color if all is well. If some expression has not been covered, however, the program text will go mostly black, and any expression that has not been evaluated will turn orange with a black background. Resolve the problem and restore your program text’s color by adding more tests.
When you’re debugging a program, it may be helpful to see the arguments that are passed to a particular function and the results that the function returns. You can enable that kind of tracing for a function with the trace declaration, which must appear after the function’s definitions.
(define (got-milk? [items : (Listof String)]) (type-case (Listof String) items [empty #f] [(cons item rst-items) (or (string=? item "milk") (got-milk? rst-items))])) (trace got-milk?)
> (got-milk? empty)
- Boolean
>(got-milk? '())
<#f
#f
> (got-milk? '("cookies" "milk"))
- Boolean
>(got-milk? '("cookies" "milk"))
>(got-milk? '("milk"))
<#t
#t
As you’re devloping a program, sometimes it’s useful to run a partial program where you haven’t yet decided on part of the implementation. The .... expression (with four dots) can be used in place of any expression of any type. A program using .... can compile and run, but the .... reports an error if it is reached during evaluation.
> (define (got-milk? [items : (Listof String)]) (type-case (Listof String) items [empty #f] [(cons item rst-items) ....])) - Void
> (got-milk? '()) - Boolean
#f
> (got-milk? '("cheese")) - Boolean
reached a `....` placeholder
1.9 Anonymous Functions
After we define a function, the name of the function can be used as a value without calling it. If you just evaluate the function name, then Plait will print something like #<procedure>.
> (define (plus-five n) (+ n 5)) > plus-five - (Number -> Number)
#<procedure:plus-five>
More usefully, you might pass the function to another function that calls it. For example, the map function takes a function and a list, and it applies the function to each element of the list to produce a new list.
> (map plus-five '(1 2 3)) - (Listof Number)
'(6 7 8)
Sometimes, and especially with map, you need a one-off function that doesn’t need to be defined for everyone else to see, and it doesn’t even need a name. You can make an anonymous function by using lambda:
> (map (lambda (n) (+ n 6)) '(1 2 3)) - (Listof Number)
'(7 8 9)
The form (lambda (n) (+ n 6)) means “the function that takes an argument n and returns (+ n 6).” You can evaluate a lambda form without passing it anywhere, although that isn’t particularly useful:
> (lambda (n) (+ n 7)) - (Number -> Number)
#<procedure>
Notice that the result has a function type: it’s a function that takes a Number and returns a Number.
An anonymous function created with lambda doesn’t have to stay anonymous. Since you can use a lambda form anywhere that an expression is allowed, you can use in define:
(define plus-eight : (Number -> Number) (lambda (n) (+ n 8)))
This definition is completely equivalent to the function-definition shorthand:
(define (plus-eight [n : Number]) : Number (+ n 8))
Another interesting property of lambda functions is that, just like any local function, the body of a lambda can see any surrounding variable binding. For example, the lambda body in the following add-to-each function can see the m that is passed to add-to-each:
> (define (add-to-each m items) (map (lambda (n) (+ n m)) items)) > (add-to-each 7 '(1 2 3)) - (Listof Number)
'(8 9 10)
> (add-to-each 70 '(1 2 3)) - (Listof Number)
'(71 72 73)
You can declare types for lambda arguments and results similar to declaring them with define in the function-definition shorthand:
(lambda ([s : String]) : Boolean (> (string-length s) 10)) - (String -> Boolean)
#<procedure>
1.10 S-Expressions
If we write (+ pi pi), then given our earlier definition of pi, the result is as you’d expect:
> (+ pi pi) - Number
6.28
We could add a ' to the front of that expression and get a
completely different result—
> '(+ pi pi) - (Listof Symbol)
'(+ pi pi)
If you’re studying programming languages and building interpreters, this looks like a handy coincidence. You can represent an expression as a list! Unfortunately, this trick does not always work:
> '(+ 1 2) eval:192:0: typecheck failed: Symbol vs. Number
sources:
(quote (+ 1 2))
+
1
A list cannot contain a mixture of numbers and symbols, so it cannot directly represent the expression (+ 1 2).
If you’ve had some experience programming in Java, you might think that the solution is a list of Objects, because anything can be coerced to and from the type Object. That is, we would be able to mix a symbol as Object with two numbers as Objects.
Plait doesn’t have an Object type, but it does have an S-Exp type, which is similar. Even better, the S-Exp type works with a convenient '-like shortcut. The S-Exp shortcut is ` (usually pronounced “backquote”) instead of ':
> `(+ 1 2) - S-Exp
`(+ 1 2)
When an S-expression is list-like, then you can corece the S-expression to a list using s-exp->list. The result is a list of S-Exps:
> (s-exp->list `(+ 1 2)) - (Listof S-Exp)
(list `+ `1 `2)
If an S-expression isn’t list-like, the coercion fails. Other coercions include s-exp->number and s-exp->symbol. You can go the other way with number->s-exp, symbol->s-exp, and list->s-exp. Functions like s-exp-list? and s-exp-number? report whether an S-expression is list-like or number-like.
> `1 - S-Exp
`1
> (s-exp->list `1) - (Listof S-Exp)
s-exp->list: not a list: `1
> (s-exp->number `1) - Number
1
> (number->s-exp 1) - S-Exp
`1
> (list->s-exp (list (symbol->s-exp '+) (number->s-exp 1) (number->s-exp 2))) - S-Exp
`(+ 1 2)
> (s-exp-number? `1) - Boolean
#t
> (s-exp-list? `1) - Boolean
#f
The backquote ` versus forward quote ' distinction is subtle. A convention to help highlight the difference is to mostly use curly braces with `. Curly braces are interchangable with parentheses and square brackets, and Plait won’t print results with curly braces, but the visual cue can still help when reading programs.
> `{+ 1 2} - S-Exp
`(+ 1 2)
> `{* 3 {+ 4 x}} - S-Exp
`(* 3 (+ 4 x))
The S-expression ` has an extra feature that the list-constructing ' lacks: a way to escape back to the evaluated-expression world by using , (i.e., a comma). The escaped expression must produce a S-expression, and the result S-expression takes the place of the escape:
> `{+ 1 ,(number->s-exp (+ 3 4))} - S-Exp
`(+ 1 7)
The ,@ escape form is similar to ,, but ,@ is a splicing escape that expects a list of S-expressions and inlines the elements into the enclosing list-like S-expression.
> `{+ ,@(list (number->s-exp 1) (number->s-exp (+ 3 4)))} - S-Exp
`(+ 1 7)
> `{+ ,(list->s-exp (list (number->s-exp 1) (number->s-exp (+ 3 4))))} - S-Exp
`(+ (1 7))
1.11 S-Expression Matching
Since the equal? function works on any kind of value, it can compare two S-expressions to determine whether they are the same:
> (equal? `{+ 1 2} `{+ 1 2}) - Boolean
#t
> (equal? `{+ 1 2} `{+ 1 4}) - Boolean
#f
Suppose that you don’t just want to recognize `{+ 1 2}, but you want to recognize any list-like S-expression that has three elements where the first element is '+-like and the other two elements are number-like. That recognition problem is tedious to implement, due to the many required many checks and coercions.
> (define (is-plus-numbers? se) (and (s-exp-list? se) (let ([l (s-exp->list se)]) (and (= 3 (length l)) (let ([a (first l)]) (and (s-exp-symbol? a) (eq? '+ (s-exp->symbol a)))) (s-exp-number? (second l)) (s-exp-number? (third l)))))) > (is-plus-numbers? `{+ 1 2}) - Boolean
#t
> (is-plus-numbers? `1) - Boolean
#f
> (is-plus-numbers? `{+ 3 y}) - Boolean
#f
> (is-plus-numbers? `{{+} 1 2}) - Boolean
#f
The s-exp-match? function simplifies recognition tasks for S-expressions. It’s like equal? on S-expressions, but the first S-expression can have special symbols that match different classes of values, instead of matching themselves literally. The special symbols include NUMBER, which matchs any number, so is-plus-numbers? is more simply implemented like this:
> (define (is-plus-numbers? se) (s-exp-match? `{+ NUMBER NUMBER} se)) > (is-plus-numbers? `{+ 1 2}) - Boolean
#t
> (is-plus-numbers? `{+ 3 y}) - Boolean
#f
Other special symbols include SYMBOL, which matches any symbol, and ANY, which matches anything.
> (define (single-argument-lambda? se) (s-exp-match? `{lambda {SYMBOL} ANY} se)) > (single-argument-lambda? `{lambda {x} {+ x 1}}) - Boolean
#t
> (single-argument-lambda? `{lambada 0}) - Boolean
#f
The symbol ... is even more special. It causes the preceeding S-expression to match zero or more times to cover multiple elements in an enclosing list. For example, `{SYMBOL ...} would match a list-like S-expression that has any number of symbol-like elements.
> (define (any-argument-lambda? se) (s-exp-match? `{lambda {SYMBOL ...} ANY} se)) > (any-argument-lambda? `{lambda {x} {+ x 1}}) - Boolean
#t
> (any-argument-lambda? `{lambda {x y z} {+ x 1}}) - Boolean
#t
> (any-argument-lambda? `{lambda {} {+ x 1}}) - Boolean
#t
> (any-argument-lambda? `{lambada 0}) - Boolean
#f
1.12 Tuples and Options
If you want to combine a small number of values in a single value, and if the values have different types (so that a list doesn’t work), you can use a tuple as an alternative to creating a new datatype with a single variant.
The values form creates a tuple from any number of values. The type of a tuple reveals the type of every component value in the tuple, separating the types with *.
> (values 1 "milk" 'apple) - (Number * String * Symbol)
(values 1 "milk" 'apple)
> (values '(1 2 3) #f) - ((Listof Number) * Boolean)
(values '(1 2 3) #f)
Using values, this consume function can effectively return two values each time that it is called:
(define (consume [s : String]) : (Symbol * String) (cond [(equal? s "milk") (values 'drink "Mmm....")] [(equal? s "beets") (values 'eat "Ugh....")] [else (values 'sleep "Zzz...")]))
To extract the component values from a tuple, match the tuple with names using define-values.
> (consume "milk") - (Symbol * String)
(values 'drink "Mmm....")
> (define-values (action response) (consume "beets")) > action - Symbol
'eat
> response - String
"Ugh...."
The convenience functions fst and snd can be used in the special case of a 2-value tuple to extract the first or second component.
> (snd (consume "milk")) - String
"Mmm...."
Sometimes, instead of always returning multiple values, you’ll want a function that returns either one value or no value. A tuple is no help for that case, but Plait predefines a helpful datatype called Optionof:
(define-type (Optionof 'a) (none) (some [v : 'a]))
The 'a in this definition of Optionof indicates that you can return any kind of value in a some.
> (define (get-slogan [s : String]) : (Optionof String) (cond [(equal? s "milk") (some "It does a body good")] [else (none)])) > (get-slogan "milk") - (Optionof String)
(some "It does a body good")
> (get-slogan "iced tea") - (Optionof String)
(none)
> (type-case (Optionof String) (get-slogan "moon pie") [(some s) s] [(none) "no comment"]) - String
"no comment"
1.13 Programs and Modules
When you write a program using #lang plait, you are technically defining a module. A Plait module contains a mixture of expressions and definitions. The expressions are evaluated in order, and the value of each expression is printed after the expression is evaluated (unless the result value has type Void). The order of function definitions doesn’t matter, as long as a function definition appears before any expression that eventually calls the function.
#lang plait (define (is-odd? x) (if (zero? x) #f (is-even? (- x 1)))) (is-odd? 0) ; ok #;(is-odd? 1) ; won't work, because it needs is-even? (define (is-even? x) (if (zero? x) #t (is-odd? (- x 1)))) (is-even? 1) ; ok (is-odd? 1) ; ok
Note the use of #; in the example above. A #; comments out the entire form that follows it, which is handy for commenting out a definition of expression, even when the definition or expression spans multiple lines.
Modules written with the module form can be nested in other modules. A nested module is called a submodule. Plait programs don’t often use submodules that are written with module, but the module+ form is more common. A module+ form creates a submodule by merging all module+s that use the same name. A typical use of module+ is to move all of a program’s tests into a test submodule.
#lang plait (define (is-odd? x) (if (zero? x) #f (is-even? (- x 1)))) (module+ test (is-odd? 0) (is-odd? 1)) (define (is-even? x) (if (zero? x) #t (is-odd? (- x 1)))) (module+ test (is-even? 1) (is-odd? 1))
The submodule name test is special, because DrRacket automatically runs a test submodule (if one is present) after running the enclosing module. In the above example, since the test submodule is run after the encloding module that defines is-odd? and is-even?, the tests can use all of the functions. Another advantage of putting tests in a test submodule is that you can turn off the tests. In DrRacket’s Language menu, select Choose Language, click Show Details, click Submodules to run, and then uncheck the test item.
A Plait module’s definitions are automatically exported from the module. You can import the definitions of another module by using the require form, typically with a string that is a relative path to the module to import.
"math.rkt"
#lang plait (define pi 3.14) (define tau (+ pi pi))
"circle.rkt"
#lang plait (require "math.rkt") (define (circle-area [r : Number]) : Number (* pi (* r r)))
A submodule created by module+ automatically imports the bindings of the enclosing module, which is why (module+ test ....) submodules can automatically access definitions for testing. In contrast, if you write definitions inside (module+ test ....), then the definitions can be used for tests in any (module+ test ....), but the enclosing module will not see the definitions.
1.14 State
Warning: If you are using Plait with a programming-languages course, then the instructor has almost certainly disallowed the constructs in this chaper for use in your homework solutions, except as specifically allowed. Don’t use set!, begin, boxes, or vectors unless the instructor says that you can. If you’re tempted to use one of those, you’re doing it wrong.
We have so far described define as naming constants, but names bound by define are not necessarily constant. The value associated to the name can be changed using set!.
> (define gravity 6.6e-11) > gravity - Number
6.6e-11
> (set! gravity 6.5e-11) - Void
> gravity - Number
6.5e-11
The type of a set! expression is Void, meaning that it doesn’t return a useful value, and the useless value doesn’t even print as a result. If you need to change a variable and then return a value, use begin to sequence the operations. The value of a begin form is the value of its last expression.
> (define counter 0)
> (define (fresh-number!) (begin (set! counter (add1 counter)) counter)) > (fresh-number!) - Number
1
> (fresh-number!) - Number
2
The ! at the end of fresh-number! is a convention to warn readers that calling the function can have a side effect.
Although you can set a variable’s value using set!, you can’t directly pass a variable to another function that changes the variable’s value. A set! on a function’s argument would change the argument variable’s value, but would have no effect on the caller’s variables. To make a mutable location that can be passed around, Plait supports boxes. You can think of a box as a mutable object that has a single field, where box creates a fresh object, unbox extracts the object’s field, and set-box! changes the object’s field.
> (define counter1 (box 0)) > (define counter2 (box 0))
> (define (fresh-number-at! c) (begin (set-box! c (add1 (unbox c))) (unbox c))) > (fresh-number-at! counter1) - Number
1
> (fresh-number-at! counter1) - Number
2
> (fresh-number-at! counter2) - Number
1
> (fresh-number-at! counter1) - Number
3
A vector is a traditional mutable array. Every element of a vector must have the same type, which can be inferred from the value that you suply when making a vector to serve as the initial value for each of the vector’s slots. The vector function creates a vector, vector-ref accesses a slot value by position, and vector-set! changes a slot value by position.
> (define counters (make-vector 2 0))
> (define (fresh-number-at-index! i) (begin (vector-set! counters i (add1 (vector-ref counters i))) (vector-ref counters i))) > (fresh-number-at-index! 0) - Number
1
> (fresh-number-at-index! 0) - Number
2
> (fresh-number-at-index! 1) - Number
1
> (fresh-number-at-index! 0) - Number
3
2 Definitions
The body of a plait module is a sequence of definitions, expressions and type declarations. The module implicitly exports all top-level definitions. When a plait module is imported into a module that does not use plait, the imports have contracts (matching reflecting the exported bindings’ types).
syntax
(id : type)
Added in version 1.1 of package plait.
syntax
(define id expr)
(define id : type expr) (define (id id/type ...) expr) (define (id id/type ...) : type expr)
id/type = id | [id : type]
For an introduction, see the tutorial section Definitions.
Defines id.
The expr in each of the first two forms is evaluated to get the value of id. In the first form, the type of id is inferred at the type of expr, while the second form declares a specific type for id.
The third and fourth forms define id as a function, where each id/type is a function argument (with an optional declare type) and expr is the body of the function, which is evaluated when the function is called. In the fourth form, a type before the body expr declares the function’s result type (which must match the type of expr).
Note that the first and second forms of define also define functions in the case that expr produces a function, such as when expr is a lambda form. The third and fourth forms are simplify shorthands for defining a function.
Evaluating a reference to id before its definition is evaluated triggers an “undefined identifier” error.
> (define a 1) > a - Number
1
> (define b : Number (+ 1 2)) > b - Number
3
> (define (c x) (+ x b)) > (c 3) - Number
6
> (define (d [y : Number]) : Number (c y)) > (d 4) - Number
7
syntax
(define-values (id/type ...) expr)
id/type = id | [id : type]
For an introduction, see the tutorial section Tuples and Options.
Defines each id/type (with an optional type declaration) to be the values within the tuple produced by expr, which must have as many values as declared id/types.
> (define t (values 1 'one "One")) > (define-values (a b c) t) > a - Number
1
> (define-values ([x : Number] [b : Symbol] [c : String]) t) > c - String
"One"
syntax
(define-type tyid/abs (variant-id [field-id : type]) ...)
tyid/abs = id | (id 'arg-id ...)
For an introduction, see the tutorial section Datatypes.
Defines a type (when tyid/abs is id) or type constructor (when tyid/abs has the form (id 'id ...)).
A constructor variant-id is defined for each variant. Each constructor takes an argument for each field of its variant, where the type of each field is declared by the type after each field-id. The result type of each constructor is id.
Instances of a type declared with define-type are normally used through type-case.
In addition to the type and constructors, a define-type expression also defines:
for each variant, a predicate variant-id? that returns #t when applied to an instance of variant-id and #f for any other value; and
for each field of each variant, an accessor variant-id-field-id that takes a instance of variant-id and returns the value of the field corresponding to field-id.
> (define-type Shape (circle [radius : Number]) (rectangle [width : Number] [height : Number])) > (define cr (circle 10)) > cr - Shape
(circle 10)
> (circle? cr) - Boolean
#t
> (circle-radius cr) - Number
10
> (define rc (rectangle 2 3)) > (+ (rectangle-width rc) (rectangle-height rc)) - Number
5
syntax
(define-type-alias tyid/abs type)
tyid/abs = id | (id 'arg-id ...)
> (define-type-alias Size Number)
> (define (square-area [side : Size]) (* side side)) > (square-area 10) - Number
100
Except for arg-ids, the type form must not reference any type variables that do not yet have a scope.
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] ...)
For an introduction, see the tutorial section Programs and Modules.
Imports from each module-path.
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 plait.
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 ...)
For an introduction, see the tutorial section Testing and Debugging.
Traces subsequent calls—
syntax
(module id module-path form ...)
(module sub plait (define n 8)) (require 'sub) (+ n 1)
syntax
(module+ id form ...)
For an introduction, see the tutorial section Programs and Modules.
Declares/extends a submodule named id, which is particularly useful for defining a test submodule to hold tests that precede relevant definitions (since the submodule implicitly imports the bindings of its enclosing module, and DrRacket or raco test runs the test submodule):
(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 ...)
3 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-Exp) written with quasiquote 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
> (has-type 1 : Number) - Number
1
> (has-type "a" : Number) eval:24:0: typecheck failed: String vs. Number
sources:
"a"
Number
syntax
(quote q-form)
q-form = id | Number | String | Boolean | (q-form ...) | #(q-form ...) | #&q-form
For an introduction, see the tutorial section Lists.
The quote form is usually written as just a ' before a q-form; that is, 'id and (quote id) are equivalent.
The quote form produces a symbol, number, string, boolean, list, vector, or box value:
A quoted id produces a symbol. For example, 'hello produces the symbol whose letters are hello.
A quoted Number, String, or Boolean produces the Number, String, or Boolean itself. For example, '1 is the same as 1.
A quoted parenthesized form produces a list containing the quoted values within the parentheses. For example, '(1 2 3) produces a list containing 1, 2, and 3. Similarly, '(a b c) produces a list containing three symbols, since 'a, 'b, and 'c are symbols.
A quoted #(q-form ...) produces a vector. The vector is immutable, though, so vector-set! will not work on the vector.
A quoted #&q-form produces a box. The box is immutable, though, so set-box! will not work on the box.
Beyond the syntactic contraints of q-form, the resulting list must have a type. So, for example, quote cannot create a list that mixes numbers and symbols.
> 'a - Symbol
'a
> '(1 2 3) - (Listof Number)
'(1 2 3)
> '(1 a) eval:27:0: typecheck failed: Number vs. Symbol
sources:
(quote (1 a))
1
a
> '((a) (b c)) - (Listof (Listof Symbol))
'((a) (b c))
syntax
(quasiquote qq-form)
qq-form = id | Number | String | Boolean | (qq-form ...) | (unquote expr) | (unquote-splicing expr) | (quasiquote expr)
syntax
syntax
For an introduction, see the tutorial section S-Expressions.
The quasiquote form is similar to quote, but it produces an S-expression, and it supports escapes via unquote and unquote-splicing. A (unquote expr) form is replaced with the value of expr, while a (unquote-splicing expr) form requires that expr produces a list and is replaced by the list content as an inlined sequence of S-expressions.
The quasiquote form is usually written as just a ` before qq-form; that is, 'qq-form and (quasiquote qq-form) are equivalent.
The unquote form is usually written as just a , before expr; that is, ,expr and (unquote expr) are equivalent.
The unquote-splicing form is usually written as just a ,@ before expr; that is, ,@expr and (unquote-splicing expr) are equivalent.
With a nested quasiquote form, escapes are preserved while escaping to the enclosing level of quotation. For example, ``(,(+ 1 2)) is equivalent to '(quasiquote (unquote 1)) where the quasiquote and unquote symbols are preserved in the result S-expression.
> `a - S-Exp
`a
> `(1 a) - S-Exp
`(1 a)
> `(+ ,(number->s-exp (+ 1 2)) 3) - S-Exp
`(+ 3 3)
> `(+ ,@(list `1 `2) 3) - S-Exp
`(+ 1 2 3)
syntax
(#%app expr expr ...)
> (add1 1) - Number
2
For an introduction, see the tutorial section Anonymous Functions.
An anonymous function which takes as many argument as specified id/tys and produces the result of expr. Each argument has an optional type specification, and when a type is written after (id/ty ...), it declares the result type of the function.
> (lambda (x) (+ x 1)) - (Number -> Number)
#<procedure>
> (lambda ([x : Number]) (+ x 1)) - (Number -> Number)
#<procedure>
> ((lambda (x) (+ x 1)) 3) - Number
4
> (map (lambda (x) (+ x 1)) (list 1 2 3)) - (Listof Number)
'(2 3 4)
syntax
syntax
(if test-expr expr expr)
syntax
(cond [test-expr expr] ...)
(cond [test-expr expr] ... [else expr])
For an introduction, see the tutorial section Conditionals.
An if form produces the value of the first expr if test-expr produces true or the value of the second expr otherwise. Only one of the two exprs is evaluated.
A cond form produces the value of the first expr whose test-expr produces true. The test-exprs are tried in order until a true result is found, and at most one of the exprs is evaluated. If no test-expr produces a true result, a “no matching clause“ exception is raised. An else in place of the last test-expr is equivalent to #t.
Each test-expr must have type Boolean.
> (if (< 1 2) 'less 'greater-or-equal) - Symbol
'less
> (cond [(< 2 1) 'bad] [(< 2 2) (begin (/ 1 0) 'oops)] [(< 2 3) 'ok] [(< 2 (/ 1 0)) 'oops] [else (begin (/ 1 0) 'oops)]) - Symbol
'ok
syntax
(case val-expr [(id-or-number ...) expr] ...)
(case val-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.
> (case (+ 1 2) [(0 1 2) 'too-small] [(3) 'ok] [else 'other]) - Symbol
'ok
> (case 'goodbye [(hello) 'hi] [(goodbye) 'bye]) - Symbol
'bye
syntax
(begin expr ...+)
For an introduction, see the tutorial section State.
Evaluates the exprs in sequence, producing the result of the last expr.
syntax
(local [definition-or-type-declaration ...] expr)
syntax
(letrec ([id rhs-expr] ...) expr)
syntax
(let ([id rhs-expr] ...) expr)
syntax
(let* ([id rhs-expr] ...) expr)
For an introduction, see the tutorial section Definitions.
Local binding forms. The local form accommodates multiple definitions and type declarations (using :) that are visible only among the definitions and the body expr. The letrec, let, and let* forms bind each id to the value of the corresponding rhs-expr (where the rhs-exprs are evaluated in order). In the case of letrec, each id is visible to every rhs-expr as well as in the body expr. In the case of let, each id is visible only in the body expr. In the case of let*, each id is visible only to later rhs-exprs as well as in the body expr.
> (local [(add-x : (Number -> Number)) (x : Number) (define (add-x y) (+ x y)) (define x 2)] (add-x 3)) - Number
5
> add-x eval:46:0: add-x: free variable while typechecking
in: add-x
> (letrec ([add-x (lambda (y) (+ x y))] [x 2]) (add-x 3)) - Number
5
> (let ([x 1]) (let ([add-x (lambda (y) (+ x y))] [x 2]) (add-x 3))) - Number
4
> (let ([x 1]) (let* ([add-x (lambda (y) (+ x y))] [x 2]) (add-x 3))) - Number
4
> (let ([x 1]) (let* ([x 2] [add-x (lambda (y) (+ x y))]) (add-x 3))) - Number
5
syntax
(shared ([id expr] ...) expr)
syntax
(parameterize ([param-expr val-expr] ...) expr)
> (define current-mode (make-parameter 'straight))
> (define (display-line) (display (case (parameter-ref current-mode) [(straight) "---"] [(curvy) "~~~"])))
> (parameterize ([current-mode 'curvy]) (display-line))
- Void
~~~
> (display-line)
- Void
---
> (define f (parameterize ([current-mode 'curvy]) (lambda () (display-line)))) > (f)
- Void
---
syntax
(set! id expr)
For an introduction, see the tutorial section State.
Mutates id to have the value of expr.
For an introduction, see the tutorial section Conditionals.
Boolean combinations with short-circuiting: as soon as an expr produces false in and or true in or, the remaining exprs are not evaluated. The value of (and) is true and (or) is false. The exprs must have type Boolean.
> (and (< 1 2) (< 3 4)) - Boolean
#t
> (and (< 2 1) (< 3 (/ 1 0))) - Boolean
#f
> (or (< 2 1) (< 3 4)) - Boolean
#t
> (or (< 2 1) (< 1 2) (< 3 (/ 1 0))) - Boolean
#t
syntax
(list elem ...)
For an introduction, see the tutorial section Lists.
Builds a list. All elems must have the same type.
> (list 1 2 3) - (Listof Number)
'(1 2 3)
> (list "a" "b") - (Listof String)
'("a" "b")
> (list (list 1 2) empty (list 3 4)) - (Listof (Listof Number))
'((1 2) () (3 4))
syntax
(vector elem ...)
For an introduction, see the tutorial section State.
Builds a vector. All elems must have the same type.
> (vector 1 2 3) - (Vectorof Number)
'#(1 2 3)
> (vector "a" "b") - (Vectorof String)
'#("a" "b")
> (vector (list 1 2) empty (list 3 4)) - (Vectorof (Listof Number))
'#((1 2) () (3 4))
syntax
(values elem ...)
For an introduction, see the tutorial section Tuples and Options.
Combines multiple values into tuple, where a tuple containing one value is equivalent to the value. Match a tuple result using define-values.
The type of each elem is independent.
> (values 1 'two "three") - (Number * Symbol * String)
(values 1 'two "three")
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])
(type-case (Listof type) val-expr [list-variant expr] ...)
(type-case (Listof type) val-expr [list-variant expr] ... [else expr])
tyid/abs = id | (id type ...) list-variant = empty | (cons first-id rest-id)
For an introduction, see the tutorial section Datatypes.
Dispatches based on the variant of the result of val-expr.
In the form that has tyid/abs, val-expr must have type tyid/abs, and tyid/abs must refer to a type defined via define-type. The result is the value of expr for the variant-id that is instantiated by val-expr or the expr in an else clause if no variant-id matches. Each field-id is bound to a corresponding (by position) value of a field within the variant instance for use in the same clause’s expr.
The number of field-ids must match the number of fields declared for variant-id in the definition of tyid/abs, and either every variant-id of tyid/abs must have a clause in the type-case form or an else clause must be present.
> (define-type Shape (circle [radius : Number]) (rectangle [width : Number] [height : Number]))
> (define (area [s : Shape]) (type-case Shape s [(circle r) (* (* r r) 3.14)] [(rectangle w h) (* w h)])) > (area (circle 1)) - Number
3.14
> (area (rectangle 2 3)) - Number
6
In the (Listof type) form, val-expr must have type (Listof type). Each non-else clause is either a (cons first-id rest-id) clause or an empty clause, and both most appear without else, or at most one of those can appear with else.
> (define (my-length l) (type-case (Listof 'a) l [empty 0] [(cons a b) (+ 1 (my-length b))])) > (length '(1 2 3)) - Number
3
> (length '(a b)) - Number
2
> (try 1 (lambda () 2)) - Number
1
> (try (/ 1 0) (lambda () 2)) - Number
2
> (try (begin (error 'demo "oops") 1) (lambda () 2)) - Number
2
> (try (begin (error 'demo "oops") 1) (lambda () (/ 2 0))) - Number
/: division by zero
For an introduction, see the tutorial section Testing and Debugging.
The test form checks whether the value of the first expr matches the value of the second expr, and reports a test failure or success. If the results of the two exprs are numbers and either is inexact, the test passes as long as the difference between the numbers is less than 0.01.
The test/exn form checks whether the expr raises an exception whose error message includes the string produced by string-expr.
The test and test/exn forms have type Void, although they do not actually produce a void value; instead, they produce results suitable for automatic display through a top-level expression, and the Void type merely prevents your program from using the result.
See also print-only-errors and module+.
syntax
(time expr)
syntax
(let/cc id expr)
4 Predefined Functions and Constants
4.1 Booleans
> (not #t) - Boolean
#f
4.2 Lists
value
value
value
value
value
value
> empty - (Listof 'a)
'()
> (cons 1 empty) - (Listof Number)
'(1)
> (first (cons 1 empty)) - Number
1
> (rest (cons 1 empty)) - (Listof Number)
'()
> (define my-list (cons 1 (cons 2 (cons 3 empty)))) > my-list - (Listof Number)
'(1 2 3)
> (first my-list) - Number
1
> (rest my-list) - (Listof Number)
'(2 3)
> (first (rest my-list)) - Number
2
> (define also-my-list (list 1 2 3)) > also-my-list - (Listof Number)
'(1 2 3)
> (rest also-my-list) - (Listof Number)
'(2 3)
value
value
value
value
> (define my-list (list 1 2 3)) > (define my-other-list (list 3 4 5)) > (append my-list my-other-list) - (Listof Number)
'(1 2 3 3 4 5)
> (append my-other-list my-list) - (Listof Number)
'(3 4 5 1 2 3)
> (map add1 (list 1 2 3)) - (Listof Number)
'(2 3 4)
> (map to-string (list 1 2 3)) - (Listof String)
'("1" "2" "3")
> (filter even? (list 1 2 3 4)) - (Listof Number)
'(2 4)
> (filter odd? (list 1 2 3 4)) - (Listof Number)
'(1 3)
> (foldl + 10 (list 1 2 3)) - Number
16
> (foldl (lambda (n r) (cons (to-string n) r)) empty (list 1 2 3)) - (Listof String)
'("3" "2" "1")
> (foldr (lambda (n r) (cons (to-string n) r)) empty (list 1 2 3)) - (Listof String)
'("1" "2" "3")
> (build-list 5 (lambda (v) (* v 10))) - (Listof Number)
'(0 10 20 30 40)
4.3 Numbers
value
value
value
value
value
value
value
value
value
value
value
value
> (+ 1 2) - Number
3
> (- 10 9) - Number
1
> (/ 10 5) - Number
2
> (modulo 10 3) - Number
1
> (remainder 10 3) - Number
1
> (min 1 2) - Number
1
> (max 1 2) - Number
2
> (floor 10.1) - Number
10.0
> (ceiling 10.1) - Number
11.0
> (ceiling 10.1) - Number
11.0
> (add1 10) - Number
11
> (sub1 10) - Number
9
value
value
value
value
value
value
value
value
> (= 1 1) - Boolean
#t
> (> 1 2) - Boolean
#f
> (< 1 2) - Boolean
#t
> (zero? 1) - Boolean
#f
> (odd? 1) - Boolean
#t
> (even? 1) - Boolean
#f
4.4 Symbols
value
string->symbol : (String -> Symbol)
value
symbol->string : (Symbol -> String)
> (string->symbol "apple") - Symbol
'apple
> (symbol->string 'apple) - String
"apple"
4.5 Strings
value
value
value
string-length : (String -> Number)
value
value
string-ref : (String Number -> Char)
> (string=? "apple" "apple") - Boolean
#t
> (string-append "apple" "banana") - String
"applebanana"
> (string-length "apple") - Number
5
> (substring "apple" 1 3) - String
"pp"
> (string-ref "apple" 0) - Char
#\a
> (to-string 1) - String
"1"
> (to-string 'two) - String
"'two"
> (to-string "three") - String
"\"three\""
> (to-string (list 1 2 3)) - String
"'(1 2 3)"
> (to-string `(1 two "three")) - String
"`(1 two \"three\")"
4.6 Characters
> (char=? #\a #\b) - Boolean
#f
value
string->list : (String -> (Listof Char))
value
list->string : ((Listof Char) -> String)
> (string->list "apple") - (Listof Char)
'(#\a #\p #\p #\l #\e)
> (list->string (list #\a #\b #\c)) - String
"abc"
4.7 S-Expressions
For an introduction, see the tutorial section S-Expressions.
A S-expression typically represents program text. For example, placing a ' in from of any plait expression (which is the same as wrapping it with quote) creates an S-expression that contains the identifiers (as symbols), parenthesization (as lists), and other constants as the expression text. Various plait values, including symbols, numbers, and lists, can be coerced to and from S-expression form.
The representation of an S-expression always reuses some other plait value, so conversion to and from an S-expression is a kind cast. For example, the s-exp-symbol? function determines whether an S-expression embeds an immediate symbol; in that case, s-exp->symbol extracts the symbol, while any other value passed to s-exp->symbol raises an exception. The symbol->s-exp function wraps a symbol as an S-expression.
For interoperability of S-expressions with untyped Racket programs, see s-exp-content and s-exp.
value
s-exp-symbol? : (S-Exp -> Boolean)
value
s-exp->symbol : (S-Exp -> Symbol)
value
symbol->s-exp : (Symbol -> S-Exp)
> (s-exp-symbol? `apple) - Boolean
#t
> (s-exp->symbol `apple) - Symbol
'apple
> (s-exp->symbol `1) - Symbol
s-exp->symbol: not a symbol: `1
> (symbol->s-exp 'apple) - S-Exp
`apple
value
s-exp-number? : (S-Exp -> Boolean)
value
s-exp->number : (S-Exp -> Number)
value
number->s-exp : (Number -> S-Exp)
> (s-exp-number? `1) - Boolean
#t
> (s-exp->number `1) - Number
1
> (number->s-exp 1) - S-Exp
`1
value
s-exp-string? : (S-Exp -> Boolean)
value
s-exp->string : (S-Exp -> String)
value
string->s-exp : (String -> S-Exp)
> (s-exp-string? `"apple") - Boolean
#t
> (s-exp->string `"apple") - String
"apple"
> (string->s-exp "apple") - S-Exp
`"apple"
value
s-exp-boolean? : (S-Exp -> Boolean)
value
s-exp->boolean : (S-Exp -> Boolean)
value
boolean->s-exp : (Boolean -> S-Exp)
> (s-exp-boolean? `#f) - Boolean
#t
> (s-exp->boolean `#f) - Boolean
#f
> (boolean->s-exp #f) - S-Exp
`#f
> (s-exp-boolean? `false) - Boolean
#f
> (s-exp-symbol? `false) - Boolean
#t
value
s-exp-list? : (S-Exp -> Boolean)
value
s-exp->list : (S-Exp -> (Listof S-Exp))
value
list->s-exp : ((Listof S-Exp) -> S-Exp)
> (s-exp-list? `(1 2 3)) - Boolean
#t
> (s-exp-list? `1) - Boolean
#f
> (s-exp->list `(1 2 3)) - (Listof S-Exp)
(list `1 `2 `3)
> (list->s-exp (list `1 `2 `3)) - S-Exp
`(1 2 3)
> (list->s-exp (list 1 2 3)) eval:167:0: typecheck failed: S-Exp vs. Number
sources:
list->s-exp
(list 1 2 3)
1
value
s-exp-match? : (S-Exp S-Exp -> Boolean)
For an introduction, see the tutorial section S-Expression Matching.
Compares the first S-expression, a pattern, to the second S-expression, a target.
To a first approximation, s-exp-match? is just equal? on the two S-expressions. Unlike equal?, however, certain symbols in the pattern and can match various S-expressions within the target.
For example, `NUMBER within a pattern matches any number in corresponding position within the target:
> (s-exp-match? `(+ NUMBER NUMBER) `(+ 1 10)) - Boolean
#t
> (s-exp-match? `(+ NUMBER NUMBER) `(+ 1 x)) - Boolean
#f
> (s-exp-match? `(+ NUMBER NUMBER) `(- 1 10)) - Boolean
#f
The following symbol S-expressions are treated specially within the pattern:
`NUMBER —
matches any number S-expression `STRING —
matches any string S-expression `SYMBOL —
matches any symbol S-expression `ANY —
matches any S-expression `... —
within a list S-expression, matches any number of repetitions of the preceding S-expression within the list; only one `... can appear as an immediate element of a pattern list, and `... is not allowed within a pattern outside of a list or as the first element of a list
Any other symbol in a pattern matches only itself in the target. For example, `+ matches only `+.
> (s-exp-match? `NUMBER `10) - Boolean
#t
> (s-exp-match? `NUMBER `a) - Boolean
#f
> (s-exp-match? `SYMBOL `a) - Boolean
#t
> (s-exp-match? `SYMBOL `"a") - Boolean
#f
> (s-exp-match? `STRING `"a") - Boolean
#t
> (s-exp-match? `STRING `("a")) - Boolean
#f
> (s-exp-match? `ANY `("a")) - Boolean
#t
> (s-exp-match? `ANY `10) - Boolean
#t
> (s-exp-match? `any `10) - Boolean
#f
> (s-exp-match? `any `any) - Boolean
#t
> (s-exp-match? `(SYMBOL) `(a)) - Boolean
#t
> (s-exp-match? `(SYMBOL) `(a b)) - Boolean
#f
> (s-exp-match? `(SYMBOL SYMBOL) `(a b)) - Boolean
#t
> (s-exp-match? `((SYMBOL) SYMBOL) `((a) b)) - Boolean
#t
> (s-exp-match? `((SYMBOL) NUMBER) `((a) b)) - Boolean
#f
> (s-exp-match? `((SYMBOL) NUMBER ((STRING))) `((a) 5 (("c")))) - Boolean
#t
> (s-exp-match? `(lambda (SYMBOL) ANY) `(lambda (x) x)) - Boolean
#t
> (s-exp-match? `(lambda (SYMBOL) ANY) `(function (x) x)) - Boolean
#f
> (s-exp-match? `(SYMBOL ...) `(a b)) - Boolean
#t
> (s-exp-match? `(a ...) `(a b)) - Boolean
#f
> (s-exp-match? `(a ...) `(a a)) - Boolean
#t
> (s-exp-match? `(a ...) `()) - Boolean
#t
> (s-exp-match? `(a ... b) `()) - Boolean
#f
> (s-exp-match? `(a ... b) `(b)) - Boolean
#t
> (s-exp-match? `(a ... b) `(a a a b)) - Boolean
#t
> (s-exp-match? `((a ...) b ...) `((a a a) b b b b)) - Boolean
#t
> (s-exp-match? `((a ...) b ...) `((a a a) b c b b)) - Boolean
#f
4.8 Vector
For an introduction, see the tutorial section State.
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)
The make-vector function creates a vector of a given size and initializes all vector items to a given value. The vector-ref function accesses the value in a vector slot, and vector-set! changes the value in a slot. The vector-length function reports the number of slots in the vector.
> (define vec (make-vector 10 "apple")) > (vector-length vec) - Number
10
> (vector-ref vec 5) - String
"apple"
> (vector-set! vec 5 "banana") - Void
> (vector-ref vec 5) - String
"banana"
> (vector-ref vec 6) - String
"apple"
4.9 Boxes
For an introduction, see the tutorial section State.
The box function creates a box with an initial value for its slot, unbox accesses the current value in a box’s slot, and set-box! changes the value.
> (define bx (box "apple")) > (define bx2 bx) > (unbox bx) - String
"apple"
> (set-box! bx "banana") - Void
> (unbox bx) - String
"banana"
> (unbox bx2) - String
"banana"
4.10 Tuples
For an introduction, see the tutorial section Tuples and Options.
> (define p (pair 1 "apple")) > p - (Number * String)
(values 1 "apple")
> (fst p) - Number
1
> (snd p) - String
"apple"
4.11 Optional Values
For an introduction, see the tutorial section Tuples and Options.
value
value
value
value
value
(define-type (Optionof 'a) (none) (some [v : 'a]))
4.12 Hash Tables
value
value
value
The hash-ref function works on either kind of hash table to find the value for a given key. If the hash table contains a mapping for a given key, hash-ref returns the key’s value wrapped with some. Otherwise, hash-ref returns (none).
> (define m-ht (make-hash (list (pair 1 "apple") (pair 2 "banana")))) > (define i-ht (hash (list (pair 1 "apple") (pair 2 "banana")))) > (hash-ref m-ht 1) - (Optionof String)
(some "apple")
> (hash-ref i-ht 1) - (Optionof String)
(some "apple")
> (hash-ref m-ht 3) - (Optionof String)
(none)
Providing an immutable hash table triggers an exception.
> (define m-ht (make-hash (list (pair 1 "apple") (pair 2 "banana")))) > (hash-ref m-ht 1) - (Optionof String)
(some "apple")
> (hash-ref m-ht 3) - (Optionof String)
(none)
> (hash-set! m-ht 3 "coconut") - Void
> (hash-set! m-ht 1 "Apple") - Void
> (hash-ref m-ht 1) - (Optionof String)
(some "Apple")
> (hash-ref m-ht 3) - (Optionof String)
(some "coconut")
value
value
hash-remove : ((Hashof 'a 'b) 'a -> (Hashof 'a 'b))
> (define i-ht (hash (list (pair 1 "apple") (pair 2 "banana")))) > (hash-ref i-ht 1) - (Optionof String)
(some "apple")
> (define i-ht2 (hash-set (hash-set i-ht 1 "Apple") 3 "coconut")) > (hash-ref i-ht2 1) - (Optionof String)
(some "Apple")
> (hash-ref i-ht2 3) - (Optionof String)
(some "coconut")
> (hash-ref i-ht 3) - (Optionof String)
(none)
> (define i-ht (hash (list (pair 1 "apple") (pair 2 "banana")))) > (hash-keys i-ht) - (Listof Number)
'(1 2)
4.13 Parameters
value
make-parameter : ('a -> (Parameterof 'a))
value
parameter-ref : ((Parameterof 'a) -> 'a)
value
parameter-set! : ((Parameterof 'a) 'a -> Void)
See also parameterize.
4.14 Equality
> (equal? "apple" "apple") - Boolean
#t
> (equal? (values 1 'two "three") (values 1 'two "three")) - Boolean
#t
4.15 Other Functions
value
The current continuation is itself represented as a function. Applying a continuation function discards the current continuation and replaces it with the called one, supplying the given value to that continuation.
value
s-exp-content : no type
value
s-exp : no type
value
tuple-content : no type
value
tuple : no type
5 Types
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]))
syntax
'id
In the following example, the type checker determines that 'a must be replaced with Number:
> (define one : 'a 1) > one - Number
1
In the following example, the type checker determines that 'b stands for the argument type of a polymorphic function:
> (define (f [x : 'b]) x)
In the following examples, the type checker is unable to replace 'a consistently with the same type everywhere:
> (if (has-type #t : 'a) (has-type 1 : 'a) 2) eval:240:0: typecheck failed: Number vs. Boolean
sources:
1
#t
a
> (define x : 'a (list (has-type 1 : 'a))) eval:241:0: typecheck failed: Number vs. (Listof Number)
sources:
(list (has-type 1 : (quote a)))
Multiple uses of the same type variable (i.e., with the same id) are constrained to refer to the same type only when the uses have the same scope. A type variable’s scope is determined as follows:
When a type variable is encountered in a left-to-right parsing of a program and no same-named variable is already in scope, then the variable’s scope is set to the nearest enclosing define form, define-type form, lambda form, let right-hand side, letrec right-hand side, or let* right-hand side.
When a type variable is encountered in a left-to-right parsing of a program and some same-named variable is already in scope, the variable have the same scope (and are confined to have the same meaning).
For example, type variables introduced in separate definitions are always distinct, so in the following example, the first 'a can stand for Number while the second stands for String:
> (define one : 'a 1) > (define two : 'a "two")
A type variable used for a lambda argument is scoped to the entire lambda form, so the uses of 'a in the definitions of the following example refer back to that argument type, and the argument cannot have both Number and String type:
> (lambda ([x : 'a]) (local [(define one : 'a 1) (define two : 'a "two")] #f)) eval:244:0: typecheck failed: Number vs. String
sources:
"two"
1
a
Beware that the order of expressions can affect the scope of type variables within the expressions:
> (values (has-type 1 : 'a) (letrec ([f (lambda ([x : 'a]) x)]) f)) - (Number * (Number -> Number))
(values 1 #<procedure:f>)
> (values (letrec ([f (lambda ([x : 'a]) x)]) f) (has-type 1 : 'a)) - (('_a -> '_a) * Number)
(values #<procedure:f> 1)
6 Syntactic Literals
syntax
syntax
syntax
7 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. Polymorphic recursion is not decidable, so see #:fuel in Untyped, Lazy, and Fuel Modes.
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.
Interactive evaluation (e.g., in DrRacket’s interactions window) can redefine identifiers that were previously defined interactively or that were defined in a module as mutable. Redefinition cannot change the identifier’s type. Due to a limitation of the type checker, identifiers of polymorphic type cannot be redefined or redeclared. Type declarations are allowed in interactive evaluation, but a declared type is never treated as a polymorphic type.
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.
8 Untyped, Lazy, and Fuel Modes
Use #:untyped immediately after #lang plait to disable type checking. The syntax of a plait module is the same with and without #:untyped, but types are ignored when #:untyped is specified. An untyped Plait module can interoperate with a typed Plait module, and dynamic checks are inserted at the boundary to protect typed functions from abuse by untyped code.
Use #:lazy immediately after #lang plait to switch evaluation to lazy mode. The syntax and type system are unchanged, but argument expressions for function calls are evaluated only when forced (by a test or by printing, ultimately). A lazy Plait module will not interoperate well with an eager module.
Use #:fuel amount immediately after #lang plait to specify how much effort should be spent resolving potentially cyclic dependencies due to inference of polymorphic recursion. The default fuel is 100.
The #:untyped, #:lazy, and #:fuel modifiers can be combined, and the combination can be declared in any order.