Inneh˚ all

advertisement
TDDC74 Programming: Abstraction and Modelling
Supplement Document
SICP, Chapter 03
Innehåll
1 SICP 3.1-3.2 – Imperative state
2
1.1
Assignment & Local State-variables
. . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
Destructive Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.3
Assignment: define and set! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.4
define versus set! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.5
begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.6
Sequencing patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.7
I/O Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.7.1
Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.7.2
I/O Programming Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.7.3
Interaction Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
1.7.4
Making your own prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2 SICP 3.3.2-3.3.3 – Mutable data
9
2.1
set! versus set-mcar! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2
Coding Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1
Overview: SICP 03 – State & Modeling
This document contains supplemental information for PRAM; this material should be studied and
understood in addition to the material in SICP, Chapter 03.
1
1.1
SICP 3.1-3.2 – Imperative state
Assignment & Local State-variables
Students who already have experience with imperative programming may wonder why there is
so much emphasis here on the use of local state-variables. Why aren’t all variables accessible from
everywhere – or, alternatively, why not define all variables in the Global Environment (to ensure that
they are universally accessible)? Although there are situations in which globally-defined variables
are appropriate, they introduce risk, uncertainty, and potential bugs into a program – so this is
usually considered a Bad Thing.
Consider what would happen if our example-variable, *the-king*, was only defined globally. When
people wanted to refer to Elvis, they would have to change the global value of the-king.1
Of course, one way to solve this is to create global-variables with distinct names: *the-king-of-sweden*
and *the-king-of-graceland* and *the-king-is-the-name-of-my-dog*. Obviously, this gets
unwieldy very fast.
This is why we emphasize the use of local state-variables – and parameter-passing.
1.2
Destructive Programming
Some students over-react when we introduce commands – they seem to forget (or ignore) everything
they have learned about functional programs. Please be careful: functional programming is not just
“an interesting theoretical model of programming.” It is extremely practical and, in fact, it can
help reduce some of the problems introduced by imperative (i.e., destructive) techniques.
One common mistake is to over-use assignment inside of recursive procedures. Students should
know that code like the following is a Bad Thing – and they should understand why.
(define *count-var* 0)
(define (imperative-count lst)
(define (imp-count-helper arg)
(if (null? arg)
*count-var*
(begin (set! *count-var* (+ *count-var* 1))
(imp-count-helper (cdr arg)))))
(imp-count-helper lst))
(imperative-count ’(a b c d)) -> 4
If we get code like this on Lab Assignments, we will return it for revision; and we may ask Quiz
questions to test whether students are able to choose appropriate programming techniques for
different kinds of problems.
1
Imagine what would happen if someone in the Global Environment (Sweden) asked “who is the King?” while
*the-king* was modified this way!
2
1.3
Assignment: define and set!
It is important to understand why set! challenges the substitution model of evaluation. The
problem is not that the first argument to set! is the same as one of its remaining argument
elements. In this sense, set! is a special form similar to define: the returned value of the second
argument is bound to the first argument.
(define foo 1)
foo -> 1
(define foo 2)
foo -> 2
(define foo 1)
foo -> 1
(set! foo 2)
foo -> 2
(set! foo (+ foo 1))
foo -> 3
So the set! special form already has a way of distinguishing between the value of its “name”
argument – and any uses of the same name in the “value” part of its arguments. The crucial
difference between define and set! is how they function when they are asked to bind a value to
a name that already exists (or a name that may already exist).
The problem is visible in the calls to size in the example definition of foobar below.
(define size 1)
(define (foobar)
(set! size (+ size 1))
size)
(foobar) -> ???
If we do a substitution evaluation for an application of (foobar), we get:
(set! size (+ 1 1))
1)
(set! size 2)
1)
?!?!?
The substitution model of evaluation for this example suggests that
1. the value of size is rebound to 2, and
2. that subsequently the value returned by size is 1!!!
Clearly we need a new model of evaluation to explain how set! does, in fact, do what it does.
1.4
define versus set!
Although both define and set! are “imperative” (they bind values to names), they work in
different ways. There are two essential differences:
1. In principle, define may only be used to create new bindings. define will look within the
local scope for an existing binding with the same name: if it finds such a local binding, it will
return an error; if it does not find such a local binding, it creates the binding.2
2
WARNING! In violation of what has just been said, in practice most Scheme implementations do allow the use
of define at the top- level to assign a new value to an existing name. The motivation is this: the top-level is assumed
to be an interactive environment for program-revision and debugging – and preventing a user from using define to
change an existing binding at the top-level seems too extreme.
3
2. set! may only be used to change values of existing bindings (whether those bindings are
created with define, with argument/parameter bindings, or with let). set! will look first
within the local scope for an existing binding with the same name: if it finds such a name
locally, it will use/change the associated value; if it does not find a local binding, it will
(a) look progressively outside (“up”) its scope for a value bound to the name
(b) use the value of the first binding (or return an error if it doesn’t find one), and
(c) update the value of the name binding it used
Imagine the two interaction sequences below are occurring in parallel universes:
(define size 1)
size -> 1
(set! size 1)
-> ERROR: cannot set undefined identifier
(define size 1)
size -> 1
(define size 2)
size -> 2
; OBS!***
(set! size 2)
size -> 2
(define size (+ size 1))
size -> 3
; OBS!***
(set! size (+ size 1))
size -> 3
OBS!*** Remember: define should only be used to create new bindings; the fact that define
can be used at the top-level to rebind existing names to new values is an interactive convenience.
Writing permanent code that makes use of this “convenience” is a Very Bad Thing – and it will be
considered a serious error.
1.5
begin
Some Scheme forms – such as cond, if, lambda, etc. – automatically sequence their evaluation in a
particular order. In fact, this is often one of the distinguishing features of a special form. Otherwise,
it may be necessary to explicitly sequence the evaluation of imperative code with begin.
(if (>= (+ age incr) 0)
(begin (set! age (+ age incr))
; do this
age)
; then do this
"error: attempt to make AGE less than zero"))
Note: there is no assumed sequencing within an if sub-expression (even though the if subexpressions themselves happen in a specific order).
1.6
Sequencing patterns
Be aware that a particular implementation pattern arises when we want a procedure to a) return
a value from some data-structure, and then, b) also modify the value stored in that data-structure
(or modify the data-structure itself). Because of this, there is a common implementation pattern:
1. create a temporary let binding for the first element of a queue
4
2. then change the queue with assignment
3. then return the value bound to the let variable
One way to remember this pattern: let, set!, return
When we introduce queues, students should understand why this implementation-pattern is used.
1.7
I/O Tutorial
Controlling input and output (I/O) is often an important part of program creation and execution.
For the most part, we do not address this aspect of programming in this course. However, it does
arise in the final Lab Assignments, so below is a brief discussion of the topic.
1.7.1
Input and Output
It may not be obvious that there is a difference between the value returned by a computation –
and the fact that it is presented on some output device. In other words, we are so familiar with this
kind of interaction:
(+ 2 3)
→
5
that it is easy to overlook the fact that we use an input device to initiate this computation – and we
use an output device to see its result. In this case, the input device was a keyboard and the output
device is a screen.3 But we could easily imagine a situation where the input-device is a microphone
(“compute the result of adding 2 and 3”) and the output-device is a speaker (“five”). The existence
(or not) or choice (or not) of a particular input/output device is separate from the computation.
This is all pretty obvious, but unless this distinction is clear, it can lead to some misunderstandings.
The fact that Scheme actually displays the result of a function application is “extra” – it is not
part of the function evaluation. So, when we start creating programs that need to display results in
various ways, we will want to control this explicitly. Some issues involve: displaying certain things
after users have entered some command or request, formatting the output, and so on.
When we explicitly manage aspects of I/O, we do it with commands rather than pure functions.
This is because our programs may do different things with the same user-input (telling a gamecharacter to “move north” will have different results that depend on where the character is) –
or the programs may respond to user input by changing the state of the program (updating the
*player-place* binding) or by changing the state of the output device (display the character in a
new screen-location).
1.7.2
I/O Programming Tutorial
For the final Lab Assignments, students will need to use a number of I/O commands to manage
user-input and display for their games. The most relevant commands are read, display, and
newline.
• read “reads in” user-input
• display sends its argument to the screen
3
Leaving aside the fact that you may be reading this from a paper printout.
5
• newline helps format display-output
The use of read involves a number of subtleties.4 For the purposes of this tutorial, it takes no
arguments – rather, it pauses program execution and waits for a value from an input device, such
as a keyboard, and then returns that value.
(define (echo)
(read))
(echo)
3
<- user-entered value
3
<- returned by read
(echo)
(* 2 2)
(* 2 2)
Note that read does not evaluate anything, it simply returns the expression the way it was typed.
If more than one expression is typed, it returns the first one and the rest are evaluated in the usual
way by the interpreter.
> (read)
square (* 2 2)
square
> 4
>
<- user-entered value
<- returned by read
<- evaluated by interpreter
Note also that the order of evaluation is unspecified. For functional programming, this has not been
a problem; but now, when we care about the sequence of things, it will be important.
(define (my-sub)
(- (read) (read)))
(my-sub)
4
<- user-entered value
3
<- user-entered value
1 or -1 <- returned by read
As we will see below, there are ways to structure programs for input so that it is not necessary to
have this kind of multiple call to read, so the example above tends not to be a problem in practice.
However, it is often an issue with display, so you will often see code use begin to explicitly control
the sequencing of display expressions:
(define (my-printout name number)
(if (not name)
G̈ive me a Name!"
(begin (display Name:
")
(display name)
(newline)
(display Number: ")
(display number)
(newline))))
(my-printout ’neo 1)
Name:
neo
Number: 1
4
Note that our description here of read and display are vastly over-simplified. General coverage of this topic is
beyond the scope of this brief overview. Students can also find explanations and simple examples of display and
newline by searching in the Dr. Racket Helpdesk.
6
The examples above raise a number of questions about formatting that, for now, we are going to
ignore. However, there are two things that are important to realize already.
1. Special forms have their own evaluation rules – and this involves their own rules for sequencing.
So, for example, if will always test its first argument first, and then, if it tests true, it will
evaluate its second argument. So, it is not necessary to use begin to sequence the main
elements of an if expression – or cond or even lambda.
However, in the example above, it was necessary to sequence the order of displays within
the final if element.
2. display is not returning a value – it is making a change to the display. This can be confusing
since Scheme usually displays the returned values of evaluations. However, students should
try and be clear in their own minds about when they do and do not need to use display.
It is not necessary when one wants to display the returned value of an expression – even if
that returned value is a string (or any other data-type). Scheme will show the returned value
on the screen as a “side effect” of returning the value. It is necessary to use display if you
want to show something that normal evaluation would not show – such as an intermediate
result or a particular layout of returned values.
To see this, consider the expression below:
(define (one-two)
(display " one ")
" two")
(one-two) → one " two"
Aside from the “odd” format of the print-out, notice what is happening. If we just wanted
the string as a returned value, we could have done this:
(define (one-two)
" one two")
(one-two) → " one two"
And if we just wanted to display some things without evaluating them, we could have used
only display. But one-two is doing two independent things; one involves displaying öne"
and the other involves evaluating ’’two". To see how separate they are, consider this:
(define (one-two)
(display " one ")
" two")
(string-length (one-two)) → one 3
It would be difficult to understand this interaction without understanding the distinction
between returning a value and printing on a display. The evaluation of a display expression
is something that happens “separately,” so speak, from evaluating the rest of the procedure
to return a value. So, in the example above, display has put its argument of one on the
screen – and string-length is quite happy to evaluate its argument of ’’two" (which is the
result of evaluating one-two) – and does not complain as it would if it was actually getting
two arguments:
(string-length " one" " two") → string-length: expects 1 argument, given 2: " one" two"
7
1.7.3
Interaction Loops
These commands are useful when creating interaction loops. Here is a minimal example:5
1. read waits for user input
2. let binds the user-input to the name user-input
3. cond tests user-input
(a) if user-input is the symbol look, a description of the current place is displayed and
the interaction loop recurs
(b) if user-input is the symbol quit, the interaction loop returns the farewell-message ’bye
and terminates
(c) otherwise, the simple-loop complains that “it doesn’t know how to” < userinput > and
recurs
(define (simple-loop)
(let ((user-input (read)))
(cond ((eq? user-input ’look)
(display "You are in an empty room. A door leads North.")
(newline)
(simple-loop))
((eq? user-input ’quit) ’bye)
(else (display "In Schemer’s World it is not possible to ")
(display user-input)
(newline)
(simple-loop)))))
> (simple-loop)
look
You are in an empty room. A door leads North.
run
In Schemer’s World it is not possible to run
quit
bye
>
Note the use of let to create a binding for the result of the argument to read. This is because
read does not store a value anywhere, it simply returns it. If there were separate read calls in the
loop above, the program would pause and wait for new input each time it encountered read.
1.7.4
Making your own prompt
The example above works, but it is difficult to tell which are the commands from the user – and
which are the program-responses to those commands. The program is missing something: a user
prompt. Imagine you want to add the following prompt indicator : >>> to the interaction loop above.
You can create such a prompt with display:
(display ">>> ") → >>>
Of course, when you do this at the Scheme prompt, you do not actually see what we show above.
The result interacts with the display of the symbol used for the Scheme prompt. If your Scheme
5
In order for the following examples to work, students should be sure to have table-helper.scm in their working
directory – and to evaluate the following expression: (load table-helper.scm")
8
interpreter uses > to indicate the prompt, this is what you will see after evaluating the display
expression:
> (display ">>> ")
>>> >
To make it clearer what is happening, you could add in an newline command – or the equivalent
escape character :
> (display ">>> \n")
>>>
>
Note that it is the “\” symbol that “escapes” the n so that Scheme treats the n as a command for
newline. You can also be more creative about your prompts:
> (display " my-prompt: \n")
my-prompt:
> (display " Quarter-Life: \n")
Quarter Life:
> (display " Doom 99: \n")
Doom 99:
> (display " The Xims: \n")
The Xims:
>
Now, we can add a suitable prompt to our interaction loop:
(define (simple-loop)
(display Schemer’s World: ")
(let ((user-input (read)))
(cond ((eq? user-input ’look)
(display "You are in an empty room. A door leads North.")
(newline)
(simple-loop))
((eq? user-input ’quit) ’bye)
(else (display "In Schemer’s World it is not possible to ")
(display user-input)
(newline)
(simple-loop)))))
> (simple-loop)
Schemer’s World: look
You are in an empty room. A door leads North.
Schemer’s World: run
In Schemer’s World it is not possible to run
Schemer’s World: quit
bye
>
2
SICP 3.3.2-3.3.3 – Mutable data
By introducing set! we were able to start changing the binding between a name and a value; this
allows us to change a variable’s value as a whole. But, as we saw in SICP 2, names can be bound
to complex structures, such as trees or tables. Sometimes we would like to selectively change – or
mutate – different parts of the structures bound to variable-names. The commands set-mcar! and
set-mcdr! allow us to do that.
In Scheme standard R6 RS and onward there has been a change compared with the SICP book.
All cons-cells, in which we want to change (mutate) a part (a car- or cdr-part) must be defined as
9
a mutable cons or mutable pair. Such cons-cell i created with mcons and the parts are selected by
mcar and mcdr. A list constructed by mutable pairs is calld a mutable list. A list constructed with
cons is called a proper list.
set-mcar! and set-mcdr! are known as mutators; so our abstract interfaces can now include
constructors, selectors, predicates (“recognizers”), and mutators.
Obs! Notice how mutators change data structures! They move the pointers – the original elements
remain unchanged. And if there are any other structures that point to the original elements, those
pointers remain. This differs from other models of assignment that you may have encountered; in
some other languages the default model is that a change to a variable-value involves “changing the
value at a particular location.”
2.1
set! versus set-mcar!
OBS! An extremely common mistake is to confuse the following two expressions:
(set! (mcar some-list) some-new-value)
(set-mcar! some-list some-new-value)
These are very different – and one of them will generate an error!
Remember: set! changes the value bound to an existing name (the “left-hand side” of a name/value
frame) – and set-mcar! changes the value referenced by the car of a particular pair – i.e., a cons
cell – (or, the current car of “the right-hand side” of a name/value frame).
If foo is bound to the following structure:
<foo> --->XX----->X/
|
|
V
V
1
2
Then, set! will change this pointer :
this!
|
v
<foo> --->XX----->X/
|
|
V
V
1
2
The pointer has no car-part or cdr-part, so, (set! (mcar foo) 99) is meaningless. But (set!
foo 99) results in this:6
<foo> ---> 99
However, set-mcar! changes the car-part of a cons-cell or list structure, and will change the value
of this:
this!
|
v
<foo> --->XX----->X/
|
|
V
V
1
2
6
The pair (1 2) is removed from the diagram for clarity, but whether it still exists as an accessible structure
depends on whether other names or structures point to it.
10
So, (set-mcar! foo 99) results in this:
<foo> --->XX----->X/
|
|
V
V
99
2
Consider the following examples. Obs! They will be much clearer if students diagram each step
with box-and-pointer notation!
(require racket/mpair)
(define *a-list* (mcons ’one (mcons ’two (mcons ’three (mcons ’four ’()))))) ;
*a-list* -> (mcons ’one (mcons ’two (mcons ’three (mcons ’four ’()))))
(set! *a-list* (mcons 1 (mcons 2 (mcons 3 (mcons 4 ’())))))
*a-list* -> (mcons 1 (mcons 2 (mcons 3 (mcons 4 ’()))))
(set-mcar! *a-list* ’one)
*a-list* -> (mcons one (mcons 2 (mcons 3 (mcons 4 ’()))))
(set! (mcar *a-list*) new-one) -> set!: not an identifier in: (mcar *a-list*)"
a mutable list
(define (change-1st-num lst) ; lst is a mutable list
(if (number? (mcar lst))
(set-mcar! lst ’two)
(change-1st-num (mcdr lst))))
(change-1st-num *a-list*)
*a-list* -> (mcons one (mcons two (mcons 3 (mcons 4 (mcons 5 ’())))))
(set-mcdr! *a-list* 1)
*a-list* -> (mcons one 1)
2.2
Coding Techniques
In SICP 03, some of the code examples use if in a way that we haven’t seen before. It is important
to realize that these techniques rely on the specification:
An if expression is evaluated as follows: first, <test> is evaluated. If it yields a true
value, then <consequent> is evaluated and its value(s) is(are) returned. Otherwise
<alternate> is evaluated and its value(s) is(are) returned.
On page 268 of SICP there is an example that relies on the fact that if the first argument to if
evaluates to anything other than #f, then if evaluates its second argument:
(define (insert! key value table)
(let ((RECORD (assoc key (cdr table))))
;; checks first for key in table
(if record
;; if RECORD not-false, mutate binding
(set-cdr! record value)
;; if RECORD is FALSE, add key/value pair to table
(set-cdr! table
(cons (cons key value) (cdr table)))))
’done)
11
Vocabulary
You will be often be hearing the terms and phrases below: lectures, quizzes, and lab assignments
will assume you are familiar with the terms.
Assignment Means to “assign” a new value to a name with an existing binding.
Command A command is a procedure that has an “effect”; that is, it makes a change that
lasts beyond the evaluation of the command (the change is “permanent”). Commands return
“unspecified” values – often indicated by <void> or “void”. (See also imperative.)
Extent The time that a binding is visible (or, in effect). Contrast with scope, which is the region
in which a binding is visible.
Imperative (literally, to command ) Imperative-style programming relies on giving commands that
“permanently” change the state of the world – or the state of some procedure(s). Commands
are very useful for certain programming tasks, but it is often difficult to control and debug
imperative programs. We will study commands and imperative programming in detail in SICP
03.
12
Download