Imperative Programming in Racket
We do not cover this material in class for several reasons. You have a lot of experience doing procedural imperative programming in other languages, so most of the ideas here are not new. If you are having some trouble with Racket, or especially its imperative features, you may want to study this section in some detail. And, as always, feel free to ask any questions you may have.
Implementing Imperative Abstractions
The special form begin
enables us to create a
sequence of expressions to be evaluated in order. This can be
useful when we are doing input and output.
For example, we can use begin
to create a function
similar to the for
statement available in Python and
many other programming languages:
(define for-each (lambda (proc lst) (if (null? lst) 'done (begin (proc (first lst)) (for-each proc (rest lst))))))
for-each
performs the operation proc
on
each member of the list lst
, in order. It returns the
symbol done
as its value.
Quick Exercise: Compare and contrast for-each
with the built-in function map
. How are they alike?
How are they different?
Now, we can use the for-each
function to write another
function that displays each member of a list in order on a single
line, without displaying the return value of the expression on the
same line:
> (define displayln (lambda lst (begin (for-each display lst) (newline)))) > (displayln "The answer is: " (+ 3 4 5)) The answer is: 12
This uses two of Racket's primitive I/O procedures,
display
and newline
.
It turns out that the begin
expression in the last
procedure is not really necessary. lambda
will take
any number of expressions in its body. We could have written
the displayln
procedure like this:
> (define displayln (lambda lst (for-each display lst) (newline))) > (displayln "The answer is: " (+ 3 4 5)) The answer is: 12
Up to now, we have only written lambda
expressions
that have a single expression in their bodies. We did not need
more, because we were creating "true" functions, procedures that
computed and returned a single value.
When a lambda
expression has multiple expressions in
its body, the value of the last expression is returned as the
result of the lambda
. let
expressions,
letrec
expressions, and the consequents of
cond
expressions also take multiple expressions
without being enclosed in a begin
expression.
Now that we know about output procedures and sequencing, we can add output procedure calls to our code for debugging purposes:
> (define factorial (lambda (n) (displayln "Entering factorial with n = " n) (let ((ans (if (zero? n) 1 (* n (factorial (- n 1)))))) (displayln "Returning from factorial with " ans) ans))) > (factorial 5) Entering fact with n = 5 Entering fact with n = 4 Entering fact with n = 3 Entering fact with n = 2 Entering fact with n = 1 Entering fact with n = 0 Returning from fact with 1 Returning from fact with 1 Returning from fact with 2 Returning from fact with 6 Returning from fact with 24 Returning from fact with 120 120
Other Input/Output Statements
There are other low-level I/O statements available in Racket that you might want to be aware of:
-
write
is likedisplay
, except that it includes information about the object's representation in the output. The result is that strings are printed with surrounding quotes, symbols are printed with a single quote prefixed, and so on.> (begin (write "written string") (display " displayed string ") (write #\x) (display #\space) (display #\x) (display #\newline)) "written string" displayed string #\x x
-
read-char
reads from the console one character at a time. -
read
reads an entire datum (such as a list or number) from the console. -
eof-object?
tests to see if what was read is the end-of-file marker.
For example, we can use read
and write
to
create our own read-eval-print
loop:
> (define read-eval-print
(lambda ()
(display "--> ")
(write (eval (read)))
(newline)
(read-eval-print)))
> (read-eval-print)
--> (+ 2 3)
5
--> (* 4 5)
20
--> (cdr '(3 4 5))
(4 5)
--> (cdr 4)
cdr: expects argument of type <pair>; given 4
We use display
to print the prompt since we don't it
to have quotes around it. We use read
to read entire
expressions. We use write
to print to results so that
they contain relevant information such as quotes when they are
printed out:
> (read-eval-print) --> "3 4 5" "3 4 5" --> #\x #\x --> > ;; I hit the 'Stop' button.
Try implementing some other functions that take advantage of sequencing and I/O...
Racket is a full-bodied extension of Scheme, and it provides many,
many higher-level forms for doing I/O, such as the
file->lines
function we saw in Session 6's
mass-to-fuel problem:
(define total-fuel (lambda (filename) (apply + (map mass->fuel (map string->number (file->lines filename))))))
Sequences of Statements
As you've now seen, Racket has a special form named
begin
:
(begin <expr_1> ... <expr_n>)
that enables us to specify a sequence of expressions to evaluate. All but the last will typically be an action with a side effect, such as doing I/O or changing the value associated with a name.
The begin
expression guarantees that Racket will
execute the expressions inside it in order. For example, we can
write:
> (begin (display "The answer is: ") (display (+ 2 4 (* 4 5)))) The answer is: 26
Though we have never done so, Racket also allows sequences in the
bodies of several of the special forms we have used this semester.
These include lambda
expressions,
let
expressions, and the individual clauses of a
cond
expression. For example:
> ((lambda (x y) (display "Simulating a let expression") (newline) (display (+ 2 x (* x y)))) 4 5) Simulating a let expression ; printed by call to display 26 ; print by the REPL
And:
> (define print-sign (lambda (n) (cond ((> n 0) (display "positive") (newline) 1) ((< n 0) (display "negative") (newline) -1) (else (display "zero") (newline) 0)))) > (print-sign 4) positive 1
Wait a second... (print-sign 4)
seems to give two
outputs. Why? Let's see if we can tease them apart:
> (define sign (print-sign 4))
Positive!
> sign
1
> (display sign)
1
> (+ 1 (print-sign 4))
positive
2
> (+ 1 (display sign))
1
+: contract violation
expected: number?
given: #<void>;
argument position: 2nd
other arguments...:
The display
expression prints one of the outputs. The
REPL then prints the value of the entire expression, which is the
other. But display
itself does not return a value!
Next session, we will see another way to use sequences of statements when we find that we need to "mutate state", that is, change the value of a variable!