DrSchemes use of local is boggling me. How does it relate to "let" and "lambda".
e.g. (from HTDP) ;; add : number -> (number -> number) ;; to create a function that adds x to its input (define (add-0 x) (local ((define (x-adder y) (+ x y))) x-adder))
I would before had thought in terms of (define (add-1 x) (lambda (y)(+ x y)))
or (define (add-2 x) (define (x-adder y)(+ x y)) x-adder)
or even (define (add-3 x) (let ((x-adder (lambda(y)(+ x y)))) x-adder))
So, what is local for? How is add-2 any different from add-0?
(whether or not they're bound to functions isn't relevant, ie, some could be of the form (define (vk ...) ...); they can also be structures created using define-struct). Now you want to localize their scope. None of Scheme's existing constructs -- let, letrec, internal defines -- preserves the semantics of the original. local does. You can simply write
> ;; add : number -> (number -> number) > ;; to create a function that adds x to its input > (define (add-0 x) > (local ((define (x-adder y) (+ x y))) > x-adder))
> I would before had thought in terms of > (define (add-1 x) > (lambda (y)(+ x y)))
> or > (define (add-2 x) > (define (x-adder y)(+ x y)) > x-adder)
> or even > (define (add-3 x) > (let ((x-adder (lambda(y)(+ x y)))) > x-adder))
> So, what is local for? How is add-2 any different from add-0?
Those are all the same. But those are the wrong examples to be looking at. The problem is
- lambda only captures the case where you have a single function that you are returning immediately,
- let does not capture recursion, and
- letrec and internal definitions turn sequential definitions into parallel ones, thereby altering the result.
(If you understand the preceding bullets, then you will see why local is different and useful. If you don't understand the preceding bullets, then you don't understand the semantics of Scheme's many binding constructs adequately, and you're just the sort of person for whom local is most useful in that it minimizes surprises.)
> (whether or not they're bound to functions isn't relevant, ie, some > could be of the form (define (vk ...) ...); they can also be > structures created using define-struct). Now you want to localize > their scope. None of Scheme's existing constructs -- let, letrec, > internal defines -- preserves the semantics of the original. local > does. You can simply write
> > ;; add : number -> (number -> number) > > ;; to create a function that adds x to its input > > (define (add-0 x) > > (local ((define (x-adder y) (+ x y))) > > x-adder))
> > I would before had thought in terms of > > (define (add-1 x) > > (lambda (y)(+ x y)))
> > or > > (define (add-2 x) > > (define (x-adder y)(+ x y)) > > x-adder)
> > or even > > (define (add-3 x) > > (let ((x-adder (lambda(y)(+ x y)))) > > x-adder))
> > So, what is local for? How is add-2 any different from add-0?
> Those are all the same. But those are the wrong examples to be > looking at. The problem is
> - lambda only captures the case where you have a single function that > you are returning immediately,
> - let does not capture recursion, and
> - letrec and internal definitions turn sequential definitions into > parallel ones, thereby altering the result.
> (If you understand the preceding bullets, then you will see why local > is different and useful. If you don't understand the preceding > bullets, then you don't understand the semantics of Scheme's many > binding constructs adequately, and you're just the sort of person for > whom local is most useful in that it minimizes surprises.)
Yep, I guess you're mostly right about that. But I'm wondering why AFAICT R5RS, MIT-Scheme, UMB-scheme, nor Scheme-48 seem to recognize LOCAL? Is it because it's a dr-scheme enhancement to simplify?
Also, is there any particular discussion/chapter/etc you could point me to to improve my "understanding of the semantics of Scheme's many binding constructs"? Or is this a topic that only comes through experience of many k of code writing.
John Clonts <joh...@my-deja.com> writes: > In article <j7vya4am2m8....@sun.cs.rice.edu>, > Shriram Krishnamurthi <shri...@cs.rice.edu> wrote: > > John Clonts <jclo...@mastnet.net> writes: > > > DrSchemes use of local is boggling me. How does it relate to > > > "let" and "lambda".
> > Don't think of local in terms of let and lambda. Think of it in terms > > of define.
Uh-oh.
> > Suppose you have a bunch of definitions: > > ... > > (whether or not they're bound to functions isn't relevant, ie, some > > could be of the form (define (vk ...) ...); they can also be > > structures created using define-struct). Now you want to localize > > their scope. None of Scheme's existing constructs -- let, letrec, > > internal defines -- preserves the semantics of the original.
Yes letrec...er no, you're right. I've actually found this to be somewhat annoying at times. You can't use the letrec *values*[1] in the letrec definitions. grrr....
I tend to think of this as a (standardized) bug in letrec, although I am fairly sure that one of the mighty Matthiases would disagree.
> Ok, what about BEGIN? Does it have the proper semantics? > Oops, I just answered my own question-- this defines a and b in global > scope! Surprise to me.
I think that there's a hack possible here, though. How about something like - or ((lambda () (let ((junk #f)) (define a 12) (define a 12) (define b 13) (define b 13) (+ a b))) (+ a b))
where you essentially use the implicit begin provided by the explicit introduction of the new scope. You could even hack up a define-syntax for local ;)
> > - lambda > > - let > > - letrec
> > (If you understand the preceding bullets, then you will see why local > > is different and useful. If you don't understand the preceding > > bullets, then you don't understand the semantics of Scheme's many > > binding constructs adequately, and you're just the sort of person for > > whom local is most useful in that it minimizes surprises.)
Or, possibly, you might learn to separate your specifications of recursion and values, which define and the top-level tricks of R5RS obscure somewhat (although in an arguably useful way). ISTM that if you dropped define and the implicit begin at toplevel (you might need to twiddle the defn of load, too), that a pretty complete categorical module system just falls out. It's somewhat ugly syntactically, but...
> AFAICT R5RS, MIT-Scheme, UMB-scheme, nor Scheme-48 seem to recognize > LOCAL? Is it because it's a dr-scheme enhancement to simplify?
Got it in one.
> Also, is there any particular discussion/chapter/etc you could point me > to to improve my "understanding of the semantics of Scheme's many > binding constructs"? Or is this a topic that only comes through > experience of many k of code writing.
R5RS. The section on 'Derived expression types' under 'Binding constructs'. I've only got the info version handy at the moment, so I can't give you the paper section numbers, sorry.
david rush -- [1] R5RS - "it must be possible to evaluate each <init> without assigning or referring to the value of any <variable>"
> scope! Surprise to me.
I think that there's a hack possible here, though. How about something like - or ((lambda () (let ((junk #f)) (define a 12) (define a 12) (define b 13) (define b 13) (+ a b))) (+ a b))
where you essentially use the implicit begin provided by the explicit introduction of the new scope. You could even hack up a define-syntax for local ;)
> > - lambda > > - let > > - letrec
> > (If you understand the preceding bullets, then you will see why local > > is different and useful. If you don't understand the preceding > > bullets, then you don't understand the semantics of Scheme's many > > binding constructs adequately, and you're just the sort of person for > > whom local is most useful in that it minimizes surprises.)
Or, possibly, you might learn to separate your specifications of recursion and values, which define and the top-level tricks of R5RS obscure somewhat (although in an arguably useful way). ISTM that if you dropped define and the implicit begin at toplevel (you might need to twiddle the defn of load, too), that a pretty complete categorical module system just falls out. It's somewhat ugly syntactically, but...
> AFAICT R5RS, MIT-Scheme, UMB-scheme, nor Scheme-48 seem to recognize > LOCAL? Is it because it's a dr-scheme enhancement to simplify?
Got it in one.
> Also, is there any particular discussion/chapter/etc you could point me > to to improve my "understanding of the semantics of Scheme's many > binding constructs"? Or is this a topic that only comes through > experience of many k of code writing.
R5RS. The section on 'Derived expression types' under 'Binding constructs'. I've only got the info version handy at the moment, so I can't give you the paper section numbers, sorry.
david rush -- [1] R5RS - "it must be possible to evaluate each <init> without assigning or referring to the value of any <variable>"
> binding constructs"? Or is this a topic that only comes through > experience of many k of code writing.
R5RS. The section on 'Derived expression types' under 'Binding constructs'. I've only got the info version handy at the moment, so I can't give you the paper section numbers, sorry.
david rush -- [1] R5RS - "it must be possible to evaluate each <init> without assigning or referring to the value of any <variable>"
Posted via cut-and-paste through an xterm running telnet, since my employer's NNTP server has disappeared from our subnet. grr....
> DrSchemes use of local is boggling me. How does it relate to "let" and > "lambda".
Shriram has answered the question pretty much.
We decided that we wanted a small number of constructs in the Scheme for our teaching world and that we wanted as much semantic regularity as possible, including error semantics.
So, if you think of scope as just a way of turning a sequence of definitions into something that has a textual beginning and end (okay, that's very rough, but let it go), then you want a construct that provides a beginning and an end to what otherwise looks like a sequence of definitions. And we want the same semantics locally and at top-level. Following Scheme tradition, we want this to be an expression, which means we must also add a body to the construct. So that leaves us with
(XXXX ( ..... a sequence of definitions .....) an-expression)
That's all, and you don't need anything else. Regular syntax, regular semantics, no need for let, letrec, let*, let+, let-, let& or whatever.
David Rush <k...@bellsouth.net> writes: > > Ok, what about BEGIN? Does it have the proper semantics? > > Oops, I just answered my own question-- this defines a and b in global > > scope! Surprise to me.
> I think that there's a hack possible here, though. How about something > like - > or > ((lambda () (let ((junk #f)) > (define a 12) (define a 12) > (define b 13) (define b 13) > (+ a b))) (+ a b))
> where you essentially use the implicit begin provided by the explicit > introduction of the new scope. You could even hack up a define-syntax > for local ;)
I already said in my post that implicit begin is not the same as local. Please read it more carefully and think about it. Indeed, since implicit begin is equivalent to letrec, and you yourself have agreed that letrec is not the same as local, it follows that ...
John Clonts <joh...@my-deja.com> writes: > Ok, what about BEGIN? Does it have the proper semantics?
> (begin ( > (define a 7) > (define b 9) > (+ a b))
> Oops, I just answered my own question-- this defines a and b in global > scope! Surprise to me.
Right. But even an implicit internal begin -- as in
(lambda () (define a 7) (define b 9) ...)
does not preserve the top-level semantics.
> But then
> (local ( > (define c 12) > (define d 13) > ) > (+ c d))
> Didn't work for me, I got: > "define-values: illegal use (not at top-level) in: (#%define-values (c) > 12)"
Are you sure you're in the right language configuration? In MzScheme, you have to load
(require-library "macro.ss")
And if you're in DrScheme or DrScheme Jr, which has LOCAL built in, you need to be in the Intermediate or Advanced Student language levels:
Welcome to DrScheme Jr version 102/17, Copyright (c) 1995-2000 PLT Language: Intermediate Student
> (local ((define c 12)
(define d 13)) (+ c d)) 25
> > (If you understand the preceding bullets, then you will see why local > > is different and useful. If you don't understand the preceding > > bullets, then you don't understand the semantics of Scheme's many > > binding constructs adequately, and you're just the sort of person for > > whom local is most useful in that it minimizes surprises.)
> Yep, I guess you're mostly right about that.
Only "mostly"? (-:
But I'm wondering why
> AFAICT R5RS, MIT-Scheme, UMB-scheme, nor Scheme-48 seem to recognize > LOCAL? Is it because it's a dr-scheme enhancement to simplify?
It's a PLT Scheme enhancement. So you can use it in our implementations: DrScheme, DrScheme Jr, MzScheme. You can't trivially implement it in these other systems (to the best of my knowledge), certainly not with a simple macro as someone else suggested.
> Also, is there any particular discussion/chapter/etc you could point me > to to improve my "understanding of the semantics of Scheme's many > binding constructs"? Or is this a topic that only comes through > experience of many k of code writing.
As Samuel Johnson wrote, "one can supply arguments, but not understanding" -- that's all manuals do. To really understand why such constructs are in the language, why they are necessary, and even what their deeper semantic implications are, needs experience. You can look at the RnRS Scheme reports (see www.schemers.org), but they are too terse to be of much help here. You may get further by reading Friedman, Wand and Haynes's "Essentials of Programming Languages", or Matthias's and my notes (available for free on-line):
> > Ok, what about BEGIN? Does it have the proper semantics?
> > (begin ( > > (define a 7) > > (define b 9) > > (+ a b))
> > Oops, I just answered my own question-- this defines a and b in global > > scope! Surprise to me.
> Right. But even an implicit internal begin -- as in
> (lambda () > (define a 7) > (define b 9) > ...)
> does not preserve the top-level semantics.
Ok, can I confirm my understanding by reading this back to you in my own words? So, you're saying that my example using (begin (define ...) "preserves top-level semantics"? Meaning that the defines occur the top-level scope? Then, in your example using (lambda ()(define ...) they occur within the scope established by the define. Ok. And this is the case with the PLT (local ...) construct as well, so (at least in this respect) the (local ...) is like a ((lambda () ...))
> And if you're in DrScheme or DrScheme Jr, which has LOCAL built in, > you need to be in the Intermediate or Advanced Student language > levels:
> Welcome to DrScheme Jr version 102/17, Copyright (c) 1995-2000 PLT > Language: Intermediate Student > > (local ((define c 12) > (define d 13)) > (+ c d)) > 25
> > > (If you understand the preceding bullets, then you will see why local > > > is different and useful. If you don't understand the preceding > > > bullets, then you don't understand the semantics of Scheme's many > > > binding constructs adequately, and you're just the sort of person for > > > whom local is most useful in that it minimizes surprises.)
> > Yep, I guess you're mostly right about that.
> Only "mostly"? (-:
Right. For any value (0-100%) of "mostly". Fine, rub it in. Is that a strange frowning emoticon, or an upside-down smiling emoticon?
> But I'm wondering why > > AFAICT R5RS, MIT-Scheme, UMB-scheme, nor Scheme-48 seem to recognize > > LOCAL? Is it because it's a dr-scheme enhancement to simplify?
> It's a PLT Scheme enhancement. So you can use it in our > implementations: DrScheme, DrScheme Jr, MzScheme. You can't trivially > implement it in these other systems (to the best of my knowledge), > certainly not with a simple macro as someone else suggested.
> > Also, is there any particular discussion/chapter/etc you could point me > > to to improve my "understanding of the semantics of Scheme's many > > binding constructs"? Or is this a topic that only comes through > > experience of many k of code writing.
> As Samuel Johnson wrote, "one can supply arguments, but not > understanding" -- that's all manuals do. To really understand why > such constructs are in the language, why they are necessary, and even > what their deeper semantic implications are, needs experience. You > can look at the RnRS Scheme reports (see www.schemers.org), but they > are too terse to be of much help here. You may get further by reading > Friedman, Wand and Haynes's "Essentials of Programming Languages", or > Matthias's and my notes (available for free on-line):
John Clonts <jclo...@mastnet.net> writes: > Ok, can I confirm my understanding by reading this back to you in my own > words? So, you're saying that my example using (begin (define ...) > "preserves top-level semantics"?
Sorry to be perverse, but yes and no. In this context, I am implicitly using the phrase "preserves top-level semantics" to mean both of these two things:
- does not change the meaning of the program [1]
AND
- encapsulates definitions in a more limited scope [2]
The (begin (define ...) ...) version certainly meets [1] (trivially, since it splices the code into the top-level), but it doesn't accomplish the original purpose, which is to encapsulate the definitions in a restrictive scope [2], as you yourself observed in an earlier post.
> Meaning that the defines occur the > top-level scope? Then, in your example using (lambda ()(define ...) > they occur within the scope established by the define.
You mean "scope established by the lambda". Actually the situation is a little more tricky, since they translate into letrec's, but yes, you're on the right track.
> Ok. And this is > the case with the PLT (local ...) construct as well, so (at least in > this respect) the (local ...) is like a ((lambda () ...))
Yes, correct. In that one respect [2], they are similar. But in the equally if not more important criterion [1], they are different in an important way: because
(define x 3) (define y 2) (define z (* x y)) z
evaluates to 6,
(local ((define x 3) (define y 2) (define z (* x y))) z)
is guaranteed to always evaluate to 6, whereas the value of
((lambda () (define x 3) (define y 2) (define z (* x y)) z))
is NOT defined -- some implementations will sometimes print 6, while the same implementation at other times, and other implementations, will sometimes give an #<undefined> value and at other times give an error. In short, you cannot rely on the answer being any particular thing. These are all legal responses because this deceptively simple code, though it looks reasonable enough, is nonsensical! The reason for this is subtle; Rob Warnock explained this in a parallel thread (look for "Differences between SCM version ...").
This is why local is NOT the same as any of these other constructs. We invented local (a) so we wouldn't have to explain the subtleties that Warnock explained, and (b) so programmers, having understood the subtlety, wouldn't have to rewrite their code entirely to work around the problem.
> > > Yep, I guess you're mostly right about that.
> > Only "mostly"? (-:
> Right. For any value (0-100%) of "mostly". Fine, rub it in. Is that a > strange frowning emoticon, or an upside-down smiling emoticon?
It's not upside down, it's mirrored left-to-right: I'm left-handed.
| Yes, correct. In that one respect [2], they are similar. But in the | equally if not more important criterion [1], they are different in an | important way: because | | (define x 3) | (define y 2) | (define z (* x y)) | z | | evaluates to 6, | | (local ((define x 3) | (define y 2) | (define z (* x y))) | z) | | is guaranteed to always evaluate to 6, whereas the value of | | ((lambda () | (define x 3) | (define y 2) | (define z (* x y)) | z)) | | is NOT defined -- some implementations will sometimes print 6, while | the same implementation at other times, and other implementations, | will sometimes give an #<undefined> value and at other times give an | error.
What about:
((lambda () (begin (define x 3) (define y 2) (define z (* x y)) z)))
As R5RS says:
The <expression>s are evaluated sequentially from left to right, and the value(s) of the last <expression> is(are) returned. This expression type is used to sequence side effects such as input and output.
So, I would think the above is equivalent to (local ...), as the begin guarantees that the internal defines are evaluated sequentially. Or am I still missing something?
Boris Schaefer <bo...@uncommon-sense.net> wrote: +--------------- | What about: | ((lambda () | (begin | (define x 3) | (define y 2) | (define z (* x y)) | z))) | | As R5RS says: | The <expression>s are evaluated sequentially from left to right, and | the value(s) of the last <expression> is(are) returned. This | expression type is used to sequence side effects such as input and | output. | | So, I would think the above is equivalent to (local ...), as the begin | guarantees that the internal defines are evaluated sequentially. Or | am I still missing something? +---------------
Yes, you're still missing something, I'm afraid. Internal definitions are not themselves "expressions", they're "internal definitions" -- and as such are *exactly* equivalent to a *single* "letrec" expression. [See R5RS Section 5.2.2 "Internal definitions".] That is, your example above is *defined* to mean this:
((lambda () (begin ; now redundant (letrec ((x 3) (y 2) (z (* x y))) z)))
The "begin" contains only one expression (the "letrec"), and thus there is no "left to right" sequentiality involved at all.
Oh, and the "letrec" bindings violate the rules [R5RS 4.2.2 & 5.2.2] requiring that "it must be possible to evaluate each <init> without assigning or referring to the value of any <variable>"...
-Rob
----- Rob Warnock, 41L-955 r...@sgi.com Applied Networking http://reality.sgi.com/rpw3/ Silicon Graphics, Inc. Phone: 650-933-1673 1600 Amphitheatre Pkwy. PP-ASEL-IA Mountain View, CA 94043
Shriram Krishnamurthi <shri...@cs.rice.edu> writes: > This is why local is NOT the same as any of these other constructs. > We invented local (a) so we wouldn't have to explain the subtleties > that Warnock explained, and (b) so programmers, having understood the > subtlety, wouldn't have to rewrite their code entirely to work around > the problem.
OK, I'm convinced. There's a bug in the spec, and letrec should be replaced w/local.
So apparently 'local' would be a 'primitive expression type' in any possible R6RS? Or do you have some clever hack for implementing it in terms of lambda?
david rush -- If you give someone Fortran, he has Fortran. If you give someone Lisp, he has any language he pleases. -- Guy L. Steele Jr.
brls...@sperience.com wrote: > David Rush <k...@bellsouth.net> writes:
> > So apparently 'local' would be a 'primitive expression type' in any > > possible R6RS? Or do you have some clever hack for implementing it in > > terms of lambda?
> I think this would work:
> (define-syntax local > (syntax-rules (define) > ((local ((define var init) ...) > expr ...) > (let ((var #f) ...) > (begin (set! var init) ...) > expr ...))))
Nice try. But recall that we also want the top-levels error warnings. A language should as regular as possible and necessary, including ERROR messages. So, now try this one:
> (local ((define y (+ x 20)) (define x 10)) (* x y))
DrScheme report the following error (Intermediate):
Variable x referenced before definition or initialization
And at top-level, it's pretty much the same error message.
brls...@sperience.com writes: > I think this would work:
> (define-syntax local > (syntax-rules (define) > ((local ((define var init) ...) > expr ...) > (let ((var #f) ...) > (begin (set! var init) ...) > expr ...))))
This can't work. Note that the macro artificially binds names that are unbound, so any code that references something unbound now inadvertently finds them bound to a specific value (#f) -- if they use it in a non-boolean context, they get the wrong error message; if they use it in a boolean context, they would never know that they actually had an erroneous program and were computing with nonsense. So this is a particularly *insidious* macro.
Shriram Krishnamurthi <shri...@cs.rice.edu> writes: > This can't work. [...] > a particularly *insidious* macro.
Curses! Foiled again.
If I understood how local was usefully different from let* I would either have another go at it or give up, convinced it was impossible.
I'm sure local would handle syntactic sugar for defining procedures. This is possible to deal with in an R5RS macro.
I'm sure local will treat subsequent defines of the same variable as set!, but one might just say "use set! when you want set!" and ignore this problem.
I know let* suggests nested environment frames and local suggests just one such frame, but I can't think what other practical implications there might be besides the set! issue.
-- (for-each (lambda (str) (display (string-append (make-string (- 40 (quotient (string-length str) 2)) #\space) str)) (newline)) '("" "Bruce Lewis" "MIT 1990" " http://brl.sourceforge.net/ " "Woulda gotten away with it if not for those meddling computer scientists"))
brls...@sperience.com writes: > If I understood how local was usefully different from let* I would > either have another go at it or give up, convinced it was impossible.
let* doesn't create recursive bindings, whereas local does:
> (local ([define fact
(lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))]) (fact 10)) 3628800
> (let* ([fact (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))])
(fact 10)) reference to undefined identifier: fact
That's a pretty significant (and useful) difference.
| >let* doesn't create recursive bindings, whereas local does: | >... That's a pretty significant (and useful) difference. | | Sorry, but isn't that what 'letrec' is for? :-) +---------------
Yes, but... Letrec *doesn't* provide for sequential assignment like let*, so what do you do when you need *both*? One answer: Use nested "letrec"s. But that doesn't provide *mutual* recursion.
Another answer: Use MzScheme's "letrec*-values" (which is not in R5RS, of course), which is what I'm guessing their "local" maps into at some level:
In a letrec*-values expression, the scope of the variables of each clause includes all of the binding clauses. The clause expressions are evaluated and bound to variables sequentially.
-Rob
----- Rob Warnock, 41L-955 r...@sgi.com Applied Networking http://reality.sgi.com/rpw3/ Silicon Graphics, Inc. Phone: 650-933-1673 1600 Amphitheatre Pkwy. PP-ASEL-IA Mountain View, CA 94043
> > If I understood how local was usefully different from let* I would > > either have another go at it or give up, convinced it was impossible.
> let* doesn't create recursive bindings, whereas local does:
[forehead slap]
Ok, I'm convinced it's impossible. The best I could do would be to take my previous macro and change the initial values from #f to a procedure that throws an error, lessening the insidiousness.
Rob Warnock wrote in message <8imsfh$la3...@fido.engr.sgi.com>... >Yes, but... Letrec *doesn't* provide for sequential assignment like let*, >so what do you do when you need *both*? One answer: Use nested "letrec"s. >But that doesn't provide *mutual* recursion.
[macro-definition left as an exercise to the reader. Oh, BTW perhaps we call it 'letrec*' ? Someone will disagree, I'm afraid]
It would *definitely* look better than abusing poor old 'define'. We have 'let' for a sequence of bindings evaluated quasi-parallel, 'let*' for sequential bindings, 'letrec' for mutual recursive quasi-parallel bindings, and...
"felix" <fe...@anu.ie> writes: > It would *definitely* look better than abusing poor old 'define'.
The point of this thread is that local actually PRESERVES `define' -- it is internal define that abuses it.
local is useful. It lets programmers write their code at the top-level, get it all tested and ship-shape, then toss it into a restricted context without having to edit the code (and introduce who-knows-what errors). There is value in that.
In the same way, the PLT Scheme module ("unit") system lets programmers define, test and close over a collection of definitions and expressions, then wrap (unit ...) around the lot. That is an even more useful capability.
In both cases, I do not want to have to modify my code, both on a matter of principle, and so I can easily extract the code back out of its restricted context for testing, porting, whatever. And this is a *linguistic* solution, not one based on Emacs cruft or whatever.
| [macro-definition left as an exercise to the reader. Oh, BTW perhaps | we call it 'letrec*'? Someone will disagree, I'm afraid]
Now that I think I understand `local' (thanks to the reply of Rob Warnock to my post), I tend to think that I would prefer a `letrec*' over `local'. It really looks much nicer than `local'.