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 ?
letsuch aslet*andletrecwhich allow a set of bindings in a let to 'see' previous (let*) or each other (letrec). docs.racket-lang.org/reference/let.html