Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Dr. Scheme "local"?

27 views
Skip to first unread message

John Clonts

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
I had been learning scheme using SICP and umb-scheme, and more recently
have been studying How To Design Programs at
http://www.cs.rice.edu/CS/PLT/Teaching/Lectures/Released/Book/.

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?

Thanks,
John

Shriram Krishnamurthi

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
John Clonts <jcl...@mastnet.net> writes:

> I had been learning scheme using SICP and umb-scheme, and more recently
> have been studying How To Design Programs at
> http://www.cs.rice.edu/CS/PLT/Teaching/Lectures/Released/Book/.
>
> 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. Suppose you have a bunch of definitions:

(define v1 ...)
(define v2 ...)
...
(define vn ...)

(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

(local (
(define v1 ...)
(define v2 ...)
...
(define vn ...)
)
<expression> ...)

and its value would be the same as if you had written <expression>
after all the definitions sans the local.

> ;; 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.)

'shriram

John Clonts

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
In article <j7vya4a...@sun.cs.rice.edu>,

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.

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)"

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.

Thanks for your help,
John


Sent via Deja.com http://www.deja.com/
Before you buy.

David Rush

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
John Clonts <joh...@my-deja.com> writes:
> In article <j7vya4a...@sun.cs.rice.edu>,
> Shriram Krishnamurthi <shr...@cs.rice.edu> wrote:
> > John Clonts <jcl...@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....

Matthias Felleisen

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
John Clonts wrote:

> I had been learning scheme using SICP and umb-scheme, and more recently
> have been studying How To Design Programs at
> http://www.cs.rice.edu/CS/PLT/Teaching/Lectures/Released/Book/.
>

> 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.

Make life in a beginner course much much simpler.

-- Matthias

Matthias Felleisen

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
P.S. There is an intermezzo in the book that explains LOCAL, its syntax, its
semantics,
its pragmatics:

http://www.cs.rice.edu/CS/PLT/Teaching/Lectures/Released/Book/node98.htm

Later on, we explain LAMBDA at first as an abbreviation of LOCAL:

(lambda <parameters> <expression>)
==
(local ((define variable:hygienic <parameters) <expression>))
variable:hygienic)

No mystery here.


Shriram Krishnamurthi

unread,
Jun 13, 2000, 3:00:00 AM6/13/00
to
David Rush <ku...@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 ...

'shriram

--
Shriram Krishnamurthi shr...@cs.rice.edu
Flectere si nequeo superos, Acheronta movebo.
--Virgil, /Aeneid/

Shriram Krishnamurthi

unread,
Jun 15, 2000, 3:00:00 AM6/15/00
to
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):

http://www.cs.rice.edu/~shriram/311/
Principles of Programming Languages

'shriram

John Clonts

unread,
Jun 17, 2000, 3:00:00 AM6/17/00
to
Shriram Krishnamurthi wrote:
>
> 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.
>

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 () ...))

> > 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")

Yes, I found that after a wrote, sorry.


>
> 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):
>
> http://www.cs.rice.edu/~shriram/311/
> Principles of Programming Languages
>
> 'shriram

Ok, I'll take a look!

Thanks for you patient and thorough explanations.

Cheers,
John

Shriram Krishnamurthi

unread,
Jun 17, 2000, 3:00:00 AM6/17/00
to
John Clonts <jcl...@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.

HTH.

'shriram

Boris Schaefer

unread,
Jun 18, 2000, 3:00:00 AM6/18/00
to
Shriram Krishnamurthi <shr...@cs.rice.edu> writes:

| 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>

The surest protection against temptation is cowardice.
-- Mark Twain

Rob Warnock

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
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 rp...@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

David Rush

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
Shriram Krishnamurthi <shr...@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.

Matthias Felleisen

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
brl...@sperience.com wrote:

> David Rush <ku...@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.

-- Matthias

Shriram Krishnamurthi

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
brl...@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

brl...@sperience.com

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
Shriram Krishnamurthi <shr...@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"))

Shriram Krishnamurthi

unread,
Jun 19, 2000, 3:00:00 AM6/19/00
to
brl...@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.

'shriram

felix

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to

Shriram Krishnamurthi wrote in message ...

>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.


Sorry, but isn't that what 'letrec' is for? :-)


felix (just asking)


Rob Warnock

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
felix <fe...@anu.ie> wrote:
+---------------

| Shriram Krishnamurthi wrote in message ...
| >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.

brl...@sperience.com

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
Shriram Krishnamurthi <shr...@cs.rice.edu> writes:

> brl...@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:

[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.

--
(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/

"))

ol...@pobox.com

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
It appears Dr.Scheme's local form can be implemented via the following
re-writing rules

(local
(define ?x ?body)
...)
-->
(letrec ((?x ?body)) (local ...))

(local
(define (?x . ?args) . ?body)
...)
-->
(letrec ((?x (lambda ?args . ?body))) (local ...))

(local ...) --> (begin ...)

felix

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to

Rob Warnock wrote in message <8imsfh$la3r$1...@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.


What about something like:

(let-sequential-and-mutually-recursive-and-perhaps-followed-by-a-star
((<variable> <expression>) ...)
<body>)

[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


Shriram Krishnamurthi

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
"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.

'shriram

Shriram Krishnamurthi

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
ol...@pobox.com writes:

> It appears Dr.Scheme's local form can be implemented via the following
> re-writing rules

> [...]

No.

'shriram

Boris Schaefer

unread,
Jun 20, 2000, 3:00:00 AM6/20/00
to
"felix" <fe...@anu.ie> writes:

| [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'.

--
Boris Schaefer <bo...@uncommon-sense.net>

"The algorithm to do that is extremely nasty. You might want to mug
someone with it."
-- M. Devine, Computer Science 340

Stephan Houben

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
On 20 Jun 2000 21:13:45 -0500, Shriram Krishnamurthi <shr...@cs.rice.edu>
wrote:

What about the following conversion:

(local
(define x <foo>)
(define y <bar>)
<baz>)

==>

(let
((x #f)
(y #f))
(set! x <foo>)
(set! y <bar>)
<baz>)

With the possible exception of behaviour when an error occurs, this
should be equivalent.

Stephan

Joe Marshall

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
Shriram Krishnamurthi <shr...@cs.rice.edu> writes:

> "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.

Or one could say that it is the top-level that treats define
unusually.


brl...@sperience.com

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
ol...@pobox.com writes:

> (local
> (define ?x ?body)
> ...)
> -->
> (letrec ((?x ?body)) (local ...))

This rule won't handle mutually-recursive definitions, as subsequent
ones are nested inside prior ones.

brl...@sperience.com

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
ste...@pcrm.win.tue.nl (Stephan Houben) writes:

> What about the following conversion:

That's exactly the conversion done by my macro, except that my macro had
to stick in an extra (begin ...) because of R5RS macro rules.

> With the possible exception of behaviour when an error occurs, this
> should be equivalent.

You mean "when the programmer makes a mistake." An error might not be
thrown, especially if the variables are expected to hold boolean values,
thus the "insidiousness" of your rules and mine.

You could initialize with a procedure that throws an error rather than
#f, but in most cases, when a procedure is not the expected type, the
error message will be unhelpful to the programmer.

Shriram Krishnamurthi

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
brl...@sperience.com writes:

> You could initialize with a procedure that throws an error rather than
> #f, but in most cases, when a procedure is not the expected type, the
> error message will be unhelpful to the programmer.

... not to mention, in an implementation where you can detect for
different types of errors (eg, the exception hierarchy of PLT Scheme),
you are no longer "merely" in the realm of unhelpful error messages
but rather back in the land of insidious errors.

Thanks for responding to Houben. I'm beginning to feel like a stuck
record out here.

'shirram

Shriram Krishnamurthi

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
David Rush <ku...@bellsouth.net> writes:

> OK, I'm convinced. There's a bug in the spec, and letrec should be
> replaced w/local.

No, letrec and local are two different things. I don't have a problem
with it being there so long as something like local is present also.

> 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?

No.

'shriram

Shriram Krishnamurthi

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
Joe Marshall <jmar...@alum.mit.edu> writes:

> Shriram Krishnamurthi <shr...@cs.rice.edu> writes:
>
> > The point of this thread is that local actually PRESERVES `define' --
> > it is internal define that abuses it.
>
> Or one could say that it is the top-level that treats define
> unusually.

One could. But Scheme's top-level seems okay to me *PROVIDED* you
have a transparent REPL (as in DrScheme). There is one kind of define
for interaction, and there should be just one other kind of define in
all other contexts. The interaction define is the one that "boxes".
The others, being in a limited scope, do not need to and should not
box any values.

'shriram

brl...@sperience.com

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
Shriram Krishnamurthi <shr...@cs.rice.edu> writes:

> Thanks for responding to Houben. I'm beginning to feel like a stuck
> record out here.

Figured I owed you something for helping me understand why simulating
top-level defines is
(letrec ((x car) (y (if (eq? car x) 'easy 'hard))) y)
without local.

felix

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to

Shriram Krishnamurthi wrote in message ...

>The point of this thread is that local actually PRESERVES `define' --


>it is internal define that abuses it.


But I think when most Scheme programmers spot 'define' in a non-local
context they usually know what to expect.
With that 'local'-thingy 'define' has another (non-standard, non-customary)
meaning. This confuses more that it helps. The naming 'local'
doesn't help either: local define's already have a well known
meaning. You introduce another one.

>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.


(Personally, I avoid to write code, get it tested and toss it
somewhere without editing the code! :-)


felix


Shriram Krishnamurthi

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
"felix" <fe...@anu.ie> writes:

> Sorry, but isn't that what 'letrec' is for? :-)
>

> felix (just asking)

No, please, don't ask. Read the thread first. We've only been over
this question about ten times.

Sheesh.

'shriram

Boris Schaefer

unread,
Jun 21, 2000, 3:00:00 AM6/21/00
to
Boris Schaefer <bo...@uncommon-sense.net> writes:

| 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'.

Well, obviously I missed one of the most important points of `local',
as I missed the following (due to Shriram):

# 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.

So, well, `letrec*' would be nice, but `local' also has its place.
I hope I understand `local' now.

It's intersting however, that this generates so long a thread that
Shiram is feeling like a stuck record.

--
Boris Schaefer <bo...@uncommon-sense.net>

We all live under the same sky, but we don't all have the same horizon.
-- Dr. Konrad Adenauer

brl...@sperience.com

unread,
Jun 22, 2000, 3:00:00 AM6/22/00
to
"felix" <fe...@anu.ie> writes:

> local define's already have a well known meaning. You introduce
> another one.

Tell me if you can predict what the value of the following expression
is, then we'll talk about what is and isn't well-known.

(let ()
(define x 1)
(define y (+ x 1))
y)

brl...@sperience.com

unread,
Jun 22, 2000, 3:00:00 AM6/22/00
to
Boris Schaefer <bo...@uncommon-sense.net> writes:

> It's intersting however, that this generates so long a thread that
> Shiram is feeling like a stuck record.

It's one of those subjects that looks simpler than it is. People pay
little attention to the thread at first since the subject line seems
implementation-specific, then when they see a lot of posts they check in
to see what all the fuss is about. Thinking they have an answer, they
post something previously posted.

I'm generalizing; not everyone who posted on this thread did so by the
above process. But I think that's the explanation for why the thread
goes so long and Shriram has to repeat himself a lot. But hey, he
teaches, so he should be used to repeating himself. :-)

felix

unread,
Jun 22, 2000, 3:00:00 AM6/22/00
to

brl...@sperience.com wrote in message ...

>Tell me if you can predict what the value of the following expression
>is, then we'll talk about what is and isn't well-known.
>
>(let ()
> (define x 1)
> (define y (+ x 1))
> y)


The value of this expression is not clearly defined.
I understand your point (I think), but I still don't like
'local's syntax and would prefer a 'let'-like
binding construct. Sorry.


felix


felix

unread,
Jun 22, 2000, 3:00:00 AM6/22/00
to

Shriram Krishnamurthi wrote in message ...


Sorry, but you were using a 'local/define' in a context where
a 'letrec' would have handled it just as well (see below).
You should choose better examples to show the significant difference
of 'local'.


>Sheesh.


Never mind.


|
|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.
|

|'shriram


Shriram Krishnamurthi

unread,
Jun 22, 2000, 3:00:00 AM6/22/00
to
"felix" <fe...@anu.ie> writes:

> Sorry, but you were using a 'local/define' in a context where
> a 'letrec' would have handled it just as well (see below).

If you read the text you yourself quoted, you'd see I was explaining
why local was not the same as LET*, not letrec. It came *after* we
had worked out why local was not the same as letrec.

> You should choose better examples to show the significant difference
> of 'local'.

And I did. Numerous times.

'shriram

Rob Warnock

unread,
Jun 23, 2000, 3:00:00 AM6/23/00
to
felix <fe...@anu.ie> wrote:
+---------------

| brl...@sperience.com wrote in message ...
| >Tell me if you can predict what the value of the following expression
| >is, then we'll talk about what is and isn't well-known.
| >
| >(let ()
| > (define x 1)
| > (define y (+ x 1))
| > y)
|
| The value of this expression is not clearly defined.
+---------------

Specifically, since the above is just [R5RS 5.2.2] syntactic sugar for:

(let ()
(letrec ((x 1)
(y ((+ x 1))))
y))

...it violates the R5RS restrictions (4.2.2, 5.2.2) on assigning or
referencing variables bound with "letrec" during the evaluation of
the initial value.

0 new messages