lambda is a very important tool in our quest for meaningful abstractions to make programming easier. Some procedures are inherently simple and one-time, and it’s a shame to spend the extra time writing out their full definitions and making up names for them. So anonymous procedures, created with
lambda, are the answer.
Here is some of the code from the previous sections rewritten with
lambda instead of explicitly defined auxiliary functions:
(defun sum (term a next b) (if (> a b) 0 (+ (funcall term a) (sum term (funcall next a) next b)))) (defun pi-sum (a b) (sum (lambda (x) (/ 1.0 (* x (+ x 2)))) a (lambda (x) (+ x 4)) b)) (defun integral (f a b dx) (* (sum f (+ a (/ dx 2.0)) (lambda (x) (+ x dx)) b) dx))
The authors also presented
let for defining temporary variables. I’ve been using
let in some of the code already written for the previous sections, so there is no need to reintroduce it here.
Here is the perverse invocation the authors refer to:
(defun f (g) (funcall g 2)) (f #'f)
f is passed as an argument to
f, it will be called with 2 as the argument. But what is
f, again ? A function that calls its argument as a function on 2. So, the second call will attempt to call 2 as a function (on 2), which is an error. CLISP complains:
*** - FUNCALL: 2 is not a function name; try using a symbol instead
Here is the CL implementation of the fixed point search and finding the square root of 2 using it:
(defvar tolerance 0.00001) (defun fixed-point (f first-guess) (labels ( (close-enough? (v1 v2) (< (abs (- v1 v2)) tolerance)) (try (guess) (let ((next (funcall f guess))) (if (close-enough? guess next) next (try next))))) (try first-guess))) (defun average (a b) (/ (+ a b) 2)) (defun dampen-sqrt (x) (fixed-point (lambda (y) (average y (/ x y))) 1.0))
I’m using here the
labels form of CL instead of an internal
defun, following a good advice given in the comments to the previous SICP post. In CL, unlike in Scheme, an internal
defun creates a function with global scope, so it doesn’t really do what we want it to do – which is define a localized function that is seen only inside the function definition it appears in.
labels is the correct way to do this in CL. There are other forms that can achieve this goal, like
flet – for differences between them turn to the Common Lisp Hyperspec
Note that the authors define
close-enough? as an explicit function with a name, instead of using a
lambda. This makes sense in this case, because the name
close-enough? is meaningful and using the explicit function makes the code more readable. On the other hand, in the definition of
dampen-sort the authors choose to represent the function computing the average of y and x/y as a
lambda, because it doesn’t harm readability and there is no point in making up a special function name for this singular case. There are no hard rules set in stone about when to use
lambda and when to use an explicit function. In some cases one is better, in some the other. My rule of thumb in such situations is to go with the intuition – whatever feels more natural and readable in any specific case. This intuition improves with experience.
First let’s prove that phi is a fixed point of
x -> 1 + 1/x. It’s quite simple, since:
phi^2 = phi + 1 (1)
So substituting phi into the transformation:
1 + 1/phi = (phi + 1)/phi = / Applying (1) (phi^2)/phi = phi
So, indeed phi is a fixed point of
x -> 1 + 1/x. Now, the code that computes it:
(fixed-point (lambda (x) (1+ (/ 1 x))) 1.0) => 1.618
Here is the code:
(defvar tolerance 0.00001) (defun fixed-point (f first-guess) (labels ( (close-enough? (v1 v2) (< (abs (- v1 v2)) tolerance)) (try (guess) (format t "Trying ~F~%" guess) (let ((next (funcall f guess))) (if (close-enough? guess next) next (try next))))) (try first-guess))) (defun average (a b) (/ (+ a b) 2)) (defun xx (x) (/ (log 1000) (log x))) (defun dampen-xx (x) (average x (xx x)))
Running without dampening:
(print (fixed-point #'xx 2.0)) => Trying 2.0 Trying 9.965784 Trying 3.0044723 Trying 6.279196 Trying 3.7598507 Trying 5.215844 Trying 4.182207 Trying 4.827765 Trying 4.3875937 Trying 4.67125 Trying 4.481404 Trying 4.6053658 Trying 4.523085 Trying 4.5771146 Trying 4.541383 Trying 4.5649033 Trying 4.5493727 Trying 4.5596066 Trying 4.552854 Trying 4.5573053 Trying 4.5543694 Trying 4.5563054 Trying 4.5550284 Trying 4.5558705 Trying 4.555315 Trying 4.555681 Trying 4.55544 Trying 4.5555987 Trying 4.5554943 Trying 4.555563 Trying 4.5555177 Trying 4.5555477 Trying 4.555528 Trying 4.555541 4.5555325
34 steps. Now, with dampening:
(print (fixed-point #'dampen-xx 2.0)) => Trying 2.0 Trying 5.982892 Trying 4.9221687 Trying 4.6282244 Trying 4.5683465 Trying 4.5577307 Trying 4.55591 Trying 4.555599 Trying 4.5555468 4.5555377
9 steps. Dampening certainly makes the computation converge much quicker in this case.
Here is the implementation:
(defun cont-frac (n d k) (labels ( (frac (i) (/ (funcall n i) (+ (funcall d i) (if (= i k) 0 (frac (1+ i))))))) (frac 1)))
This is a recursive process, of course. It takes k = 11 to reach 4-digit accuracy in the computation of 1/phi:
(print (cont-frac (lambda (i) 1.0) (lambda (i) 1.0) 11)) => 0.6180556
And this is the iterative version:
(defun cont-frac-iter (n d k) (labels ( (frac-iter (i result) (if (= i 0) result (frac-iter (1- i) (/ (funcall n i) (+ (funcall d i) result)))))) (frac-iter k 0)))
As usual, it is a bit less elegant and obvious. I wonder if anyone first produces the iterative version for these exercises, and only then the recursive one. Somehow, resursive functions often greatly simplify matters. In the case of
cont-frac, for instance, the recursive function is almost a transcription of the formula into a program. The iterative version, on the other hand, takes somewhat more thought to understand – because it has to generate the result from the bottom up (note that it counts down while the recursive function counts up).
The only trick here is to realize the rule by which the elements of
Di are generated, and this isn’t too hard if you notice that the non-1s are all 3 elements away from each other and are successive multiples of 2.
(print (cont-frac (lambda (i) 1.0) (lambda (i) (let ((i+1 (1+ i))) (if (= (rem i+1 3) 0) (* 2.0 (/ i+1 3)) 1.0))) 10)) => 0.7182817
e - 2!
Again, transforming the fraction into a recursive process is very straightforward:
(defun tan-cf (x k) (labels ((tan-step (i) (/ (if (= i 1) x (square x)) (- (1- (* i 2)) (if (= i k) 0 (tan-step (1+ i))))))) (tan-step 1)))