A regular reader writes:
Hello Robert:
I have a question about block structure in scheme. I think we had talked about this a little while back — about it being bad specifically — and I was wondering if you could tell me again what your thoughts were about it? I am looking specifically at the square root approximation code in SICP chapter 1 (pages 29–30).
Thank you.
Scheme Irritates Computer Programmers
Kansas City
Hello.
Okay, the following is really my opinion about so-called “block structure”. I won’t call it “block structure” though, I’ll call it “nested definitions”, because “block structure” is actually a more general term.
I am not a fan of nested definitions with the syntax Scheme provides. For instance
(define (sqrt x) (define (good-enough? guess x) ...) ...)
I do not like the syntax of this. The “internal define” has no clear delimited scope. It is “context sensitive”. You must look to see there is an outer define wrapped around the inner define to notice that its scope is limited to the end of the outer define.
Compare the following two examples:
(define (add-doubles a b) (let ((double-a (* 2 a)) (double-b (* 2 b))) (+ double-a double-b)))
versus (the invalid but hypothetical)
(define (add-doubles a b) (let double-a (* 2 a)) (let double-b (* 2 b)) (+ double-a double-b))
In the first one, the scope of double-a and double-b is clearly demarcated by the let construct. The end of the let means the end of the scope. In the second one, the lets are just sort of floating around. A novice programmer might be inclined to sprinkle lets anywhere in the code.
I see define the same way. They are just sort of floating around, without an immediately clear end to their scope (though, if you’re curious, their scope is well defined by the standard).
But, like I said, this is a purely syntactic argument. Internal definitions are in fact good, and keep the namespace clean.
So what’s the alternative?
It’s somewhat clunkier and I know why SICP didn’t introduce it at this point in the book. It would confuse readers probably and require heaps of explanation.
Instead of writing
(define (sqrt x) (define (good-enough? guess x) ...) ...)
we write
(define (sqrt x) (letrec ((good-enough? (lambda (guess x) ...))) ...))
Notice how, like let, letrec clearly demarcates the scope. We can have multiple items in the letrec too, for multiple definitions.
Anyway, this is a very fine distinction and is more opinion/philosophically incorrect than functionally. As I said, the standard supports nested defines just fine with unambiguous meaning.
P.S. As an interesting exercise, see Challenge #3 in the $(20+n)$ Challenges for Great Justice here. It intends to give the best of both worlds: define syntax and clearly delimited scope.
I can’t see any of the code examples.
For some reason, all of the code samples on my site have been clobbered. Looking into it.