Up to now, we have exclusively used immutable values in our programs, but Typed Racket, like many functional programming languages, also supports imperative programming with mutable state. In this class, we will use mutable vectors and mutable structs. Typed Racket also allows mutable variables, but do not use them.
Mutable vectors
The basic operation for updating a mutable vector is
(: vector-set! : (All (a) (Vectorof a) Integer a -> Void))
The Void
return type means that this function does not return any value.
The use of the |
The vector quotation syntax produces vectors that are immutable, which means that
you will get a runtime error if you try to operate on them with |
Sequencing operations
With operations, such as vector-set!
, that return Void
, our code will often
become a sequence of updates. We use the expression form
(begin exp1 exp2 ... expn)
for this purpose; it has the effect of executing exp1
, exp2
, … in turn.
Its result is the value of expn
.
For example, we can use it to define a function that swaps two elements of
a vector:
(: swap! : (All (A) (Vectorof A) Integer Integer -> Void))
;; swap the ith and jth elements of the vector
(define (swap! v i j)
(local
{(define tmp (vector-ref v i))}
(begin (vector-set! v i (vector-ref v j))
(vector-set! v j tmp))))
Sometimes we need an expression of type Void
(e.g., to make the types
of a conditional match). We can use the void
function, which takes no
arguments, for this purpose:
(: void : -> Void)
For example, the following expression swaps the i
th and j
th elements
of a vector when the i
th element is greater than the j
th.
(if (> (vector-ref v i) (vector-ref v j))
(swap! v i j)
(void))
There is also a function for-each
that allows one to apply a function
to the elements of a list for effect only:
(: for-each : (All (X) (X -> Void) (Listof X) -> Void))
For example, the following expression prints each symbol in the list on its own line.
(for-each (lambda ([x : Symbol]) (begin (display x) (newline))) '(A B C))
Mutable structs
In addition to mutable vectors, we will also use mutable structs.
These are defined by adding the #:mutable
option to the struct definition
as in the following example:
(define-struct Pt
([x : Real] [y : Real])
#:mutable)
When we include the #:mutable
option in a struct declaration, Typed Racket
will generate additional functions for modifying the struct (one for each declared field).
In our Pt
example, we get two additional operations.
(: set-Pt-x! : Pt Real -> Void)
(: set-Pt-y! : Pt Real -> Void)
that can be used to update in place the x
and y
fields of the struct.
For example, here is a function that moves a point by the given offsets.
(: pt-move! : Pt Real Real -> Void)
(define (pt-move! p dx dy)
(begin
(set-Pt-x! p (+ (Pt-x p) dx))
(set-Pt-y! p (+ (Pt-y p) dy))))
Input/Output
We only use simple input/output (I/O) operations in this class. Typed Racket provides a function for reading a line of text from the user.
(: read-line : -> (U String EOF))
(: eof-object? : Any -> Boolean : EOF)
This function returns either a string of the input text or the special EOF
(end of file)
value. You can use eof-object?
to test for EOF
.
The display
function will write any type of value to the screen.
(: display : Any -> Void)
Since display
does not force a newline after its output, you can use the newline
function to force a linebreak.
(: newline : -> Void)
We can combine display
and newline
together to implement a helpful debugging
function.
(: show : (All (A) A -> A))
(define (show x)
(begin
(display x)
(newline)
x))
This function displays its argument and returns it, which means that you can wrap it around any expression to see what the result of the expression is.