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 ! suffix is a Racket naming convention used to signify functions that work imperatively.

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 vector-set!.

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 ith and jth elements of a vector when the ith element is greater than the jth.

(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.

Last updated 2020-03-04 09:35:23 -0600
Table of Contents | Back to CS151 Home