Section #2: =========== 0. General point: "Don't try to use side effects!" 1. Dylan is a _functional_ language - so _everything_ has a value - this makes the expression (if test consequent) have no meaning when test will evaluate to false. This is unlike Pascal/C where statements _do_ something (side effect) like printing or assignment - here an if statement with no alternate part will just "do nothing" if the test is false... Dylan, however, must return some value - our implementation could decide on simply returning #f as the value of (if #f something), but this is something you shouldn't rely on (as all other "it-will-probably-do-this" things). 2. Reminder - the parens can be compared to C parens - they mean apply something - this is the reason why (+ (1) (2)) won't work - written in C syntax that is "+(1(), 2())" but 1 isn't a function (method) so "1()" is an error. 3. Show some examples for a multiple argument methods with/without types - that should clear the reason for having double parens in (method ((x )) (* x x)) 4. Go again (if needed) on the difference between "1", "(method () 1)" and "((method () 1))". Also remind that method is a special form - the reson that "(method () (/ 1 0))" works and "((method () (/ 1 0)))" don't. 5. Method arguments with types have a meaning of checking the input arguments - for example we can define: "(define square1 (method (x) (* x x)))" "(define square2 (method ((x )) (* x x)))" and then when we apply "(square1 #t)" we get an error when trying to apply the multiplication but "(square2 #t)" returns an error as soon as we try to apply square2. Note: the preferable style in Dylan is to specify types whenever you know what the type should be. Actually writing "(define square1 (method (x) (* x x)))" is the same in Dylan as writing: "(define (square1 ) (method ((x )) (* x x)))" is the "mother-of-all-types"... 6. Again - the important difference between _syntax_ and _semantics_ - a good way to think about this is the difference between the string "42" stored in a file somewhere (two ASCII values), and the number "42" stored in memory (in some representation). The evaluation function that Dylan uses is actually a function that takes a piece of syntax and returns its semantics. 7. Evaluation using the substitution model: Use this printout (since they will probably not have it): --- (define (heron-sqrt ) (method ((x )) ; Try some guess for sqrt(x) ; we always pick 1. (try 1 x))) (define (try ) (method ((guess ) (x )) ;; If it's good enough guess (if (good-enough? guess x) ;; Then stop and return the old guess guess ;; Otherwise, try an improved guess. (try (improve guess x) x) ))) (define (good-enough? ) (method ((guess ) (x )) (< (abs (- (square guess) x)) 0.0001))) (define (improve ) (method ((guess ) (x )) (/ (+ guess (/ x guess)) 2.0))) (define (square ) (method ((x )) (* x x))) --- And then do an evaluation example (from l02-dylan.text): --- Here's a more complicated example: 1. (sqrt 2) 2. [ {proc (( x)) (try 1 x)} {2} ] 3. (try 1 {2}) 4. [ {proc (( guess) ( x)) ...} {1} {2} ] *** If is a special form, so we handle it specially (has its own rule in the model): 5. (if (good-enough? {1} {2}) {1} (try (improve {1} {2}) {2}) ) By the rule for if, we first evaluate the test: [ {proc (( guess) ( x)) ...} {1} {2} ] (< (abs (- (square {1})) {2}) 0.01) ... #f Now by the special rule for IF we evaluate the alternative because the test was false: 6. (try (improve {1} {2}) {2}) We have to evaluate this "inside out" because in order to evaluate all the subexpressions we need a value for the second subexpression which is itself a combination. We get this value by [ {proc (( guess) ( x)) ...} {1} {2} ] (average {1} (/ {1} {2})) [{proc (a b) (/ (+ a b) 2)} {1} {2}] (/ (+ {1} {2}) 2) ... {1.5} Now we have evaluated each subexpression of (try (improve {1} {2}) {2}) resulting in [ {proc (( guess) ( x)) ...} {1.5} {2}] Substituting the body of the procedure with values {1.5} and {2} We're back at something that looks like step 4. The pattern repeats. 7. (if (good-enough? {1.5} {2}) {1.5} (try (improve {1.5}{2}) {2}) The test is #f so by reasoning analogous to above, (try (improve {1.5}{2}) {2}) evaluates to {1.416667} 8. [ {proc (( guess) ( x)) ...} {1.416667} {2} ] 9. (if (good-enough? {1.416667} {2}) {1.416667} (try (improve {1.416667} {2}))) The test of the if evaluates to #t in this case so the value is thus {1.416667} --- 8. A point about substituting model evaluation: With the Substitution Model, we have a very precise model for determining the value of expressions.... AT LEAST, FOR NOW. It breaks when we add assignment, but that won't happen for quite a while. We'll write some complicated programs without it. That's part of the lesson: you don't need to depend on assignment as much as you do. Kick the habit. 9. More on special forms vs. conventional functions: Suppose we have this function: (define (fact ) (method ((n )) (if (zero? n) 1 (* n (fact (dec n)))))) [Note: introducing "zero?", "dec"] What will happen if we use new-if instead of the if statement: (define new-if (method (test consequent alternate) (if test consequent alternate))) This looks like it works fine: (new-if (> 3 2) 1 2) --> 1 (new-if (< 3 2) 1 2) --> 2 But then when you use it in "fact": (define (fact ) (method ((n )) (new-if (zero? n) 1 (* n (fact (dec n)))))) You get: (fact 4) --> stack overflow error or something similar. Why? 10. Generalizing code example: (define f (method (x) (+ x 1))) (f 3) ==> 4 That can be generalized to add something other than 1: (define f (method (x y) (+ x y))) (f 3 2) ==> 5 This is a very important technique (used in PS1). (?) We can even generalize a function and use that as an argument: (define f (method (x g) (g x 1))) (f 3 +) ==> 4 (f 3 -) ==> 2 (f 3 (method (x y) (+ (square x) (square y)))) ==> 10 A more complicated example (at the end, if have spare time): (define (square-deriv ) (method ((x )) (/ (- (* (+ x 0.0001) (+ x 0.0001)) (* x x)) 0.0001))) This is actually: (define (square ) (method ((x )) (* x x))) (define square-deriv (method ((x )) (/ (- (square (+ x 0.0001)) (square x)) 0.0001))) We can generalize the dx to get: (define square-deriv (method ((x ) (dx )) (/ (- (square (+ x dx)) (square x)) dx))) As the last punch-line - think about what we'll get if we generalize the function used: (define deriv (method ((f ) (x ) (dx )) (/ (- (f (+ x dx)) (f x)) dx)))