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