CS 61A, Summer 2006 Instructor: Kevin Lin Lecture Notes Lecture 1, 6/26/06 _A_Super_Long_Introduction_to_Scheme_ Note #1: The reading for this week is sections 1.1 and 1.3; we'll skip 1.2 for now and come back to it later. Note #2: CS 61A is a lot different from computer science classes you may have taken in the past. It's weird for some people who are used to writing iterative procedures to get used to writing recursive procedures. I think it's best to approach CS 61A with a clean slate; pretend you don't know anything about computers or programming. Approach recursive procedures as if you don't know anything about iteration. Try to understand recursion for what it is, instead of trying to draw analogies between your understanding of recursion and your understanding of iteration. Well, that's my take on it, at least. Note #3: This document might seem a bit long, and it kind of is, and I might use language that I don't define rigorously, but don't worry about the details too much. This is merely meant to be an introduction to some of the cool things that you can do in Scheme. We'll go over things again in greater detail in the lectures to come. Note #4: FYI, lecture notes in the future probably won't be as comprehensive as this one. Note #5: The best way to learn the basics of Scheme is to sit in front of the interpreter and play around with it yourself. The language we use in CS 61A is called Scheme, and we use an interpreter for it called STk. Why do we use Scheme? Because its syntax is simple and uniform, so it's very easy and quick to learn. After only one or two lectures you already will be able to read and understand fairly complex programs, whereas this would not be so easy to do in other languages such as Java or C++. Furthermore, Scheme is particularly well-suited for teaching the ideas that we are presenting in this course. What's the difference between using an interpreter versus using a compiler? A compiler takes your code and turns it into executable machine code, which you can run all at once as a whole or not at all. On the other hand on an interpreter you can do more interactive things and run only bits and pieces of code at a time as you wish. This makes things more fun and easier to deal with for beginners, and plus the STk interpreter that we use is a particularly good one (for one thing, it hides a lot of annoying details so that we can deal with stuff that we actually care about in this course--you'll have to worry about those annoying details in later courses, like CS 61C). Let's try some sample STk interactions. Everything we enter into STk is what's known as an EXPRESSION. STk then interprets (evaluates) the expression, and then spits out that expression's VALUE. In Scheme, the evaluation of every expression has a return value. [If you know C++ or Java, say, you will recall that in those languages you can specify procedures that have no return values.] Okay, let's start with some really simple stuff. STk> 3 ===> 3 [I use "===>" to denote that "3" is the return value] Let's do some arithmetic. STK> (+ 2 3) ===> 5 STk> (* 5 4) ===> 20 What's up with the funky notation? Why didn't we write "2+3" or "5*4" as we would in some other languages? In Scheme, everything uses what's called prefix notation. This means that the operator always comes first, and the operands come second. Also, every pair of parentheses in Scheme corresponds to a procedure call; without them nothing would happen. The first symbol that appears after the opening parenthesis is the operand, and the other symbols before the closing parenthesis are the operands. Again, EVERY PAIR OF PARENTHESES CORRESPONDS TO A PROCEDURE CALL (except for in a very small number of special cases, which we'll see later). As you can see, there's a lot of uniformity in what's going on, and this uniformity makes it easier for the computer and sometimes also for us humans to parse expressions. Another reason why we don't write "2+3" or "5*4" is as follows. STk> (+ 1 2 3 4 5) ===> 15 This is certainly simpler than "1+2+3+4+5". We can use the addition procedure to add up more than just two numbers, that is, the addition procedure can take more than just two arguments. (If you've never seen the word "argument" before, it just means "input to a function".) Procedure names don't just have to be punctuation characters, for example: STk> (sqrt 16) ===> 4 We can also compose functions: STk> (+ (* 2 3) 5) ===> 11 STk (- 1 (+ (* 2 3) 5 2)) ===> 12 Okay, suppose we don't want to deal with numbers; suppose we want to deal with words. STk> hello *** Error: unbound variable: hello Current eval stack: __________________ 0 hello Hmm, that attempt failed. Why? Because when we entered in "hello", STk looked around for a variable named "hello" so it could tell us its value. However, no such variable has yet been defined. So it spits out an unbound variable error. Alright, how about this: STk> 'hello hello Great! What the quote sign (or the apostrophe) does is it tells STk to take what comes after it and take it for what it is without trying to evaluate it. With the quote sign we can now input data other than numbers into our programs, namely words. Throughout this course, when we use the term WORD we will mean a quoted string of alphanumerics (plus possibly some other characters like question marks or exclamations marks, but not spaces). Numbers alone are also considered words. We have some procedures for operating on words. STK> (first 'hello) ===> h STk> (butfirst 'hello) [You can type "bf" as a shortcut for "butfirst"] ===> ello STk> (last 'hello) ===> o STk> (butlast 'hello) [You can type "bl" as a shortcut for "butlast"] ===> hell STk> (word 'he 'llo) ===> hello STk> (word 'h 'e 'l 'l 'o) ===> hello In each of the above cases the return value is another word. We can also compose these procedures with one another, as well as with arithmetic procedures. STk> (first (bf 'hello)) ===> e STk> (- (first 23) (last 45)) ===> -3 STk> (+ 1 (bl 453)) ===> 46 We can also group words together to form sentences. Sentences can also be quoted. We define a SENTENCE to be a flat sequence of words (separated by spaces and delimited by parentheses). STk> '(+ 2 3) ===> (+ 2 3) STk> '(hey teacher leave us kids alone) ===> (hey teacher leave us kids alone) Note that without the apostrophe, STk would try to evaluate these expressions and give us an error in the latter case. With the apostrophe, STk does not evaluate anything and returns the indicated sentences. Now consider the following: STk> (sentence 'hey 'teacher 'leave 'us 'kids 'alone) ===> (hey teacher leave us kids alone) STk> (sentence 1 2 3 4 5) [You can type "se" as a shortcut for "sentence"] ===> (1 2 3 4 5) STk> (se '1 'hey '2 'teacher) (1 hey 2 teacher) The return values in these cases are all sentences. The sentence procedure can also be used to form sentences out of other sentences, and out of words and other sentences. STk> (sentence '(a) '(b) 'c 'd '(e) 'f) ===> (a b c d e f) STK> (sentence '(hey teacher) '(leave us kids alone)) ===> (hey teacher leave us kids alone) STk> (sentence 'hey 'teacher '(leave us kids alone)) ===> (hey teacher leave us kids alone) The return values here are also sentences. Some of the procedures we used for words also work for sentences. STk> (first '(hey teacher leave us kids alone)) ===> hey STk> (last '(hey teacher leave us kids alone)) ===> alone The return values above are words. STk> (butfirst '(hey teacher leave us kids alone)) ===> (teacher leave us kids alone) STk> (butlast '(hey teacher leave us kids alone)) ===> (teacher leave us kids) The return values above are sentences. Now let's try something different. STk> + #[closure arglist=args 11f328] STk> first #[closure arglist=(x) 156d38] Woah, what happened there? Well, what happened was STk looked up the values of the expressions "+" and "first". The values of these expressions are the functions that they represent respectively. The way that STk prints functions is "#[closure ...]". The gibberish strings "11f328" and "156d38" at the back refer to locations in the computer where the addition and first procedures are respectively located. So that's why those strings are different; they're different procedures, so of course they will have different addresses in the computer. You don't really have to worry about this stuff, I'm just putting it in here for the sake of completeness. Alright, so far we've done some cool stuff, but nothing all that powerful. STk> (define pi 3.1415926) ===> pi [Ignore this return value, it's not important] Now we've defined a variable named "pi". It has value 3.1415926. STk> pi ===> 3.1415926 So that's nice, now we can just type "pi" instead of "3.1415926" every single time we need to use that number. STk> (* pi 5 5) ===> 28.2743334 STk> (* pi pi pi) ===> 31.0062750935697 Now let's define a procedure! STk> (define (area r) (* pi r r)) ===> area [Again ignore the return value of define] Here, "area" is the name of the procedure, "r" is called the formal parameter of the procedure (representing the radius of a circle), and "(* pi r r)" is called the body of the procedure. So this procedure takes the radius of a circle and returns the area of that circle. So suppose we want to find the area of a circle of radius 5. Then all we need to do is: STk> (area 5) ===> 28.2743334 The way this works is STk takes the argument and evaluates it, getting "5", and identifies that result with the formal parameter "r" of area. Then it takes the body of area, and replaces each "r" in the body with "5", and evaluates the resulting expression after the replacement. This is not actually what really happens, but it will do for now. Don't worry too much about details right now. Note that the "pi" that is present inside of the body of the area procedure is referring to the "pi" that we defined earlier! We can also compose user-defined procedures with built-in procedures. Indeed, user-defined procedures can be used and treated as if they were built-in procedures. STk> (area (sqrt 25)) ===> 28.2743334 STk> (+ 1 (area 5)) ===> 29.2743334 For Scheme procedure calls, ALL ARGUMENTS ARE ALWAYS EVALUATED. But wait! What about define? If this were true for define, then wouldn't (define pi 3.1415926) give us an error? We would have to evaluate "pi" before we actually defined it, which would give us an unbound variable error! But we don't encounter such a problem because define is technically not a procedure; it's what's called a SPECIAL FORM--it doesn't follow the ordinary rules, it follows its own special rules. In particular, when we call define we don't have to evaluate all of the arguments, in fact we evaluate none of them. We can also have conditional statements: STk> (if (even? 4) (+ 1 2) (+ 3 4)) ===> 3 STk> (if (odd? 4) (+ 1 2) (+ 3 4)) ===> 7 We can now define, for example, an absolute value procedure: (define (abs x) (if (< x 0) (* -1 x) x)) STk> (abs 5) ===> 5 STk> (abs -10) ===> 10 Note that if is a special form; more on this tomorrow. We can also define recursive procedures: (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 is a procedure that takes words and turns them into their Pig Latin equivalents. STk> (pigl 'scheme) ===> emeschay STk> (pigl 'spring) ===> ingspray STk> (pigl 'pig) ===> igpay More on recursion tomorrow.