I was curious about defining multiple lexically scoped functions in Scheme that can call each other. Working in SICP, I produced the following function using block structure to solve Exercise 1.8 (calculating cube-root using Newton's method):
(define (cbrt x)
(define (good-enough? guess prev-guess)
(< (/ (abs (- guess prev-guess))
guess)
0.001))
(define (improve guess)
(/ (+ (/ x (square guess))
(* 2 guess))
3))
(define (cbrt-iter guess prev-guess)
(if (good-enough? guess prev-guess)
guess
(cbrt-iter (improve guess)
guess)))
(cbrt-iter 1.0 0.0))
This works fine, but it got me wondering how Scheme (and perhaps Common Lisp) might handle this same scenario using lexical scoping and the let
form. I tried to implement it using let
with the following kludgy code:
(define (cbrt x)
(let ((calc-cbrt
(lambda (guess prev-guess)
(let ((good-enough?
(lambda (guess prev-guess)
(< (/ (abs (- guess prev-guess))
guess)
0.001))))
(good-enough? guess prev-guess))
(let ((improve
(lambda (guess)
(/ (+ (/ x (square guess))
(* 2 guess))
3))))
(improve guess))
(let ((cbrt-iter
(lambda (guess prev-guess)
(if (good-enough? guess prev-guess)
guess
(cbrt-iter (improve guess)
guess)))))
(cbrt-iter 1.0 0.0)))))
(calc-cbrt 1.0 0.0)))
The problem that I see below is when cbrt-iter
attempts to call the good-enough?
procedure. Since the good-enough?
procedure is only local to the scope of the first nested let
block, cbrt-iter
has no way to access it. It seems that this can be solved by nesting the cbrt-iter
function within the enclosing let
of good-enough
, but this seems also very kludgy and awkward.
What is the define
form doing that is different in this case? Is the define
form expanding to lambda
expressions instead of the "let over lambda" form (I recall something similar being done in the Little Schemer book using the form ((lambda (x) x x) (lambda (y) ...))
, but I am not sure how this would work). Also, by way of comparison, how does Common Lisp handle this situation - is it possible to use lexically scoped defun
's ?
let
such aslet*
andletrec
which allow a set of bindings in a let to 'see' previous (let*) or each other (letrec). docs.racket-lang.org/reference/let.html – karmakaze