Functional Problem Solving (CSC 151 2014F) : Readings

Simple Output in Scheme


Summary: We explore Scheme's basic procedures for printing output.

Introduction

While much of our exploration of Scheme will involve making and manipulating images, there are times that it will be more convenient to communicate with the user (or with ourselves) through simple text. We call this “textual output”. Among other things, textual output allows us to trace the way in which our program runs.

But textual output also changes the way we think about procedures. Before, a procedure was just something that computed a value (a number, a drawing, whatever). Now, procedures also have side effects - in addition to computing a value, they do something else. At times, we'll see a conflict between our traditional way of thinking about procedures and these new side effects.

Basic Output Procedures

Scheme provides a variety of output procedures. For now, we'll focus on two: display and newline.

In its most basic form, the display procedure takes one input argument and displays that argument to the screen. That argument can be a number, a symbol, a string, or one of a variety of other types that we will learn over the rest of the semester.

> (display 23)
23
> (display 'hello)
hello
> (display (/ 3 4))
3/4
> (display (/ 3.0 4.0))
0.75
> (display 'a) (display 23) (display 'b)
a23b

You'll note that subsequent calls to display that happen together concatenate the output together. That's an expected behavior - sometimes you want more than one thing on the same line. What should you do if you want a line between values? The newline procedure moves on to a new line before printing the next value. Strangely enough, newline takes no parameters, so you just write (newline). The form is different enough that we'll say it again: newline is a procedure, so it needs to be in parentheses, but it takes no arguments, so it is just surrounded by parentheses.

> (display 'a) (newline) (display 23) (newline) (display 'b)
a
23
b

Note: The display procedure prints its result to the screen, rather than displaying it. In part, you can tell because the output is a bit different; in DrRacket, results are blue while output is more of a purple color. But you can also tell when you try to compute with the values.

> (+ (display 1) (display 2))
12+: contract violation expected: number? given: #<void>

The nature of the value that display returns is unspecified. That is, the printing is a side effect of the evaluation of the call to write, not its result.

What do we do if we want something other than a simple symbol or a number? We can print an arbitrary set of text by surrounding the text with double quotation marks.

> (define name "The Doctor")
> (display "Hello, my name is ") (display name) (newline)
Hello, my name is The Doctor

A Simple Example: Greeting a User

Of course, we don't usually write multiple expressions per line. But we might combine them into a procedure. Let's write a simple procedure that greets the user. Our procedure takes one input, the name of the person it is greeting. It then uses multiple calls to display to display some appropriate text, which it ends with a newline.

;;; Procedure:
;;;   greet
;;; Parameters:
;;;   name, a symbol or string
;;; Purpose:
;;;   Prints a friendly message.
;;; Produces:
;;;   [Nothing; called for the side effect.]
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   Some text has been printed.
(define greet
  (lambda (name)
    (display "Hello, ")
    (display name)
    (display ".  ")
    (display "It's nice to meet you.")
    (newline)))

Let's see the procedure in action. You'll note that it works with both symbols (which begin with one single quotation mark) and strings (which are surrounded by double quotation marks).

> (greet 'Sam)
Hello, Sam.  It's nice to meet you.
> (greet 'Janet)
Hello, Janet.  It's nice to meet you.
> (greet "The Doctor")
Hello, The Doctor.  It's nice to meet you.

As this procedure suggests, Scheme includes a way to explicitly sequence operations. In the past, we've simply said “Scheme evaluates the parameters to a procedure before applying the procedure”, but it's open as to what order Scheme evaluates them. Now, we see that it's possible to do side-effecting operations in sequence. When the body of a Scheme procedure includes more than one expression, Scheme evaluates each expression in order and returns the value of the last expression.

Tracing Code

There are a wide variety of ways in which programmers use output procedures. One particularly helpful use, at least for novice programmers, is to trace the behavior of procedures. Once you've written a procedure, you can add a call to display to the start of the procedure so that you know when it's called. Doing so can help us better understand the way in which Scheme works.

Suppose we've written some simple procedures that do basic math - add, subtract, multiply, and divide. (Yes, we should also document those procedures, but their behavior should be pretty obvious.)

(define add
  (lambda (x y)
    (+ x y)))

(define subtract
  (lambda (x y)
    (- x y)))

(define multiply
  (lambda (x y)
    (* x y)))

(define divide
  (lambda (x y)
    (/ x y)))

(define negate
  (lambda (x)
    (- x)))

Consider the following expression.

(negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))

You should be able to tell an order in which Scheme might evaluate this by using our normal policy of “evalute the parameters before you apply the function”. (We say “an order” because it's not clear whether the first or second parameter is evaluated first.)

But let's say we wanted Scheme to tell us what it's doing. We start by augmenting all of the procedures.

(define add
  (lambda (x y)
    (display "add ") (display x) (display " and ") (display y) (newline)
    (+ x y)))

(define subtract
  (lambda (x y)
    (display "subtract ") (display y) (display " from ") (display x) (newline)
    (- x y)))

(define multiply
  (lambda (x y)
    (display "multiply ") (display x) (display " and ") (display y) (newline)
    (* x y)))

(define divide
  (lambda (x y)
    (display "divide ") (display x) (display " by ") (display y) (newline)
    (/ x y)))

(define negate
  (lambda (x)
    (display "negate ") (display x) (newline)
    (- x)))

Now, we can watch Scheme in action.

> (negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
negate 5
multiply 3 and 2
divide 1 by 4
subtract 1/4 from 6
add -5 and 23/4
negate 3/4
negate -3/4
3/4

This tells us a bit about how DrRacket evaluates this expression. In particular, it seems to do operations from the inside-out (as we expected), given that it multiplies before subtracting, and subtracts before negating. We might also note that given multiple parameters, it seems to evaluate them from left to right. For example, it negated 5 before it did the subtraction. We can also note that it's not very smart: We all know that if you negate twice, you get the original value back. But DrRacket does the work anyway. We can even see this as it tries to multiply a complex expression by zero.

> (multiply 0 (multiply (divide 2 3) (divide (divide 11 4) 13)))
divide 2 by 3
divide 11 by 4
divide 11/4 by 13
multiply 2/3 and 11/52
multiply 0 and 11/78
0

But this output is incomplete. While we see the operation being performed, we don't know what value it computes (except implicitly). What if we want to see the result of each operation? We need a helper procedure that prints the result. But, as we've just seen, we can't just print the result, since the display method doesn't return a value. Hence, we need to print and return the value. Here's a helper that does just that.

;;; Procedure:
;;;   yield
;;; Parameters:
;;;   val, a value
;;; Purpose:
;;;   Display a short message about val
;;; Produces:
;;;   val, the same value
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   A short message is displayed
(define yield
  (lambda (val)
    (display "  yields ")
    (display val)
    (newline)
    val))

You'll note that we ended the procedure with val. That's because Scheme uses the value of the last expression in a procedure as the value of the procedure.

Now, a little more modifications to our procedures, and we're ready for some even more interesting output.

(define add
  (lambda (x y)
    (display "add ") (display x) (display " and ") (display y) (newline)
    (yield (+ x y))))

(define subtract
  (lambda (x y)
    (display "subtract ") (display y) (display " from ") (display x) (newline)
    (yield (- x y))))

(define multiply
  (lambda (x y)
    (display "multiply ") (display x) (display " and ") (display y) (newline)
    (yield (* x y))))

(define divide
  (lambda (x y)
    (display "divide ") (display x) (display " by ") (display y) (newline)
    (yield (/ x y))))

(define negate
  (lambda (x)
    (display "negate ") (display x) (newline)
    (yield (- x))))
> (negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
negate 5
  yields -5
multiply 3 and 2
  yields 6
divide 1 by 4
  yields 1/4
subtract 1/4 from 6
  yields 23/4
add -5 and 23/4
  yields 3/4
negate 3/4
  yields -3/4
negate -3/4
  yields 3/4
3/4

Wasn't that fun? We'll often use a technique like this when we're trying to understand what our code is doing.

Self Checks

Check 1: Exploring Order of Evaluation

As the reading notes, procedures like add and divide provide a mechanism for helping us explore the order in which Scheme evaluates expressions.

Make a copy of side-effects-lab.rkt, which contains the code from the reading.

a. Verify that you get the same output from the following expression that you saw in the reading. If you don't, hypothesize why you may have seen a different result.

> (negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))

b. Turn the expression 5*7+3*4/5 into Scheme, using the named procedures. Do not evaluate your expression.

c. Predict the order in which subexpressions will be evaluated.

d. Check your answer experimentally.