Reading: Abelson & Sussman, Section 1.1 (pages 1-31)
Welcome to CS 61A, the world's best computer science course, because we use the world's best CS book as the textbook. The only thing wrong with this course is that all the rest of the CS courses for the rest of your life will seem a little disappointing (and repetitive).
Course overview comes next lecture; now we're going to jump right in so you can get started exploring on your own in the lab.
In 61A we program in Scheme, which is an interactive language. That means that instead of writing a great big program and then cranking it through all at once, you can type in a single expression and find out its value. For example:
Terminology: the formal parameter is the name of the
argument (x
); the actual argument expression is
the expression used in the invocation ((+ 2 3)
); the
actual argument value is the value of the argument in
the invocation (5). The argument's name comes from the function's
definition; the argument's value comes from the invocation.
(define (plural wd) (word wd 's)) |
This simple plural
works for lots of words (book, computer,
elephant) but not for words that end in y
(fly, spy). So we
improve it:
;;;;; In file cs61a/lectures/1.1/plural.scm (define (plural wd) (if (equal? (last wd) 'y) (word (bl wd) 'ies) (word wd 's))) |
If
is a special form that only evaluates one of the alternatives.
Pig Latin: Move initial consonants to the end of the word and append "ay"; SCHEME becomes EMESCHAY.
;;;;; In file cs61a/lectures/1.1/pigl.scm (define (pigl wd) (if (pl-done? wd) (word wd 'ay) (pigl (word (bf wd) (first wd))))) (define (pl-done? wd) (vowel? (first wd))) (define (vowel? letter) (member? letter '(a e i o u))) |
Pigl
introduces recursion – a function that invokes
itself. More about how this works next week.
Another example: Remember how to play Buzz? You go around the circle counting, but if your number is divisible by 7 or has a digit 7 you have to say "buzz" instead:
;;;;; In file cs61a/lectures/1.1/buzz.scm (define (buzz n) (cond ((equal? (remainder n 7) 0) 'buzz) ((member? 7 n) 'buzz) (else n))) |
This introduces the cond
special form for multi-way choices.
Cond
is the big exception to the rule about the meaning of
parentheses; the clauses aren't invocations.
Computer science isn't about computers (that's electrical engineering) and it isn't primarily a science (we invent things more than we discover them).
CS is partly a form of engineering (concerned with building reliable, efficient mechanisms, but in software instead of metal) and partly an art form (using programming as a medium for creative expression).
Programming is really easy, as long as you're solving small problems. Any kid in junior high school can write programs in BASIC, and not just exercises, either; kids do quite interesting and useful things with computers. But BASIC doesn't scale up; once the problem is so complicated that you can't keep it all in your head at once, you need help, in the form of more powerful ways of thinking about programming. (But in this course we mostly use small examples, because we'd never get finished otherwise, so you have to imagine how you think each technique would work out in a larger case.)
We deal with three big programming styles/approaches/paradigms:
The big idea of the course is abstraction: inventing languages that let us talk more nearly in a problem's own terms and less in terms of the computer's mechanisms or capabilities. There is a hierarchy of abstraction:
Application programs High-level language (Scheme) Low-level language (C) Machine language Architecture (registers, memory, arithmetic unit, etc) circuit elements (gates) transistors solid-state physics quantum mechanics |
In 61C we look at lower levels; all are important but we want to start at the highest level to get you thinking right.
;;;;; In file cs61a/lectures/1.1/argue.scm > (argue '(i like spinach)) (i hate spinach) > (argue '(broccoli is awful)) (broccoli is great) (define (argue s) (if (empty? s) '() (se (opposite (first s)) (argue (bf s))))) (define (opposite w) (cond ((equal? w 'like) 'hate) ((equal? w 'hate) 'like) ((equal? w 'wonderful) 'terrible) ((equal? w 'terrible) 'wonderful) ((equal? w 'great) 'awful) ((equal? w 'awful) 'great) ((equal? w 'terrific) 'yucky) ((equal? w 'yucky) 'terrific) (else w) )) |
This computes a function (the opposite
function) of each word in a
sentence. It works by dividing the problem for the whole sentence into
two subproblems: an easy subproblem for the first word of the sentence,
and another subproblem for the rest of the sentence. This second subproblem
is just like the original problem, but for a smaller sentence.
We can take pigl
from last lecture and use it to translate a whole
sentence into Pig Latin:
(define (pigl-sent s) (if (empty? s) '() (se (pigl (first s)) (pigl-sent (bf s))))) |
The structure of pigl-sent
is a lot like that of argue
. This
common pattern is called mapping a function over a sentence.
Not all recursion follows this pattern. Each element of Pascal's triangle is the sum of the two numbers above it:
(define (pascal row col) (cond ((= col 0) 1) ((= col row) 1) (else (+ (pascal (- row 1) (- col 1)) (pascal (- row 1) col) )))) |
To illustrate this point we use a modified Scheme evaluator that lets
us show the process of applicative or normal order evaluation. We
define functions using def
instead of define
. Then, we
can evaluate expressions using (applic (...))
for applicative
order or (normal (...))
for normal order. (Never mind how this
modified evaluator itself works! Just take it on faith and concentrate
on the results that it shows you.)
In the printed results, something like
(* 2 3) ==> 6 |
indicates the ultimate invocation of a primitive function. But
(f 5 9) --> (+ (g 5) 9) |
indicates the substitution of actual arguments into the body of
a function defined with def
. (Of course, whether actual argument
values or actual argument expressions are substituted depends on
whether you used applic
or normal
, respectively.)
> (load "lectures/1.1/order.scm") > (def (f a b) (+ (g a) b)) ; define a function f > (def (g x) (* 3 x)) ; another one g > (applic (f (+ 2 3) (- 15 6))) ; show applicative-order evaluation (f (+ 2 3) (- 15 6)) (+ 2 3) ==> 5 (- 15 6) ==> 9 (f 5 9) --> (+ (g 5) 9) (g 5) --> (* 3 5) ==> 15 (+ 15 9) ==> 24 24 > (normal (f (+ 2 3) (- 15 6))) ; show normal-order evaluation (f (+ 2 3) (- 15 6)) --> (+ (g (+ 2 3)) (- 15 6)) (g (+ 2 3)) --> (* 3 (+ 2 3)) (+ 2 3) ==> 5 (* 3 5) ==> 15 (- 15 6) ==> 9 (+ 15 9) ==> 24 ; Same result, different process. 24 > (def (zero x) (- x x)) ; This function should always return 0. zero > (applic (zero (random 10))) (zero (random 10)) (random 10) ==> 5 (zero 5) --> (- 5 5) ==> 0 0 ; Applicative order does return 0. > (normal (zero (random 10))) (zero (random 10)) --> (- (random 10) (random 10)) (random 10) ==> 4 (random 10) ==> 8 (- 4 8) ==> -4 -4 ; Normal order doesn't. |
The rule is that if you're doing functional programming, you get the
same answer regardless of order of evaluation. Why doesn't this hold
for (zero (random 10))
? Because it's not a function! Why not?
Efficiency: Try computing
(square (square (+ 2 3))) |
in normal and applicative order. Applicative order is more efficient because it only adds 2 to 3 once, not four times. (But later in the semester we'll see that sometimes normal order is more efficient.)
Note that the reading for next week is section 1.3, skipping 1.2 for the time being.