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

Local definitions without excessive syntax ?

20 views
Skip to first unread message

Kelly Murray

unread,
Feb 10, 1999, 3:00:00 AM2/10/99
to
I suppose I'll get crucified (yet again) on syntax to
improve the language...

Macrolet and flet are very useful,
but I find the syntax is an excessive burden.
I suggest they can just be defined by embedded definition
at the beginning of a code body. Seems to me a simple
transformation in which similiar processing is already
done to handle type declarations.

-Kelly Murray k...@intellimarket.com

(function foo (x y)
(macro stroke (s) ...)
(function length (s) ...)
...
(stroke (length 10))
)

As compared with the code below, which is difficult
to convert from having local defs to not having them,
as well as adding and deleting individual defs.

(defun foo (x y)
(macrolet (
(stroke (s) ...)
)
(flet (
(length (s) ...)
)
...
(stroke (length 10))
)
)
)

Barry Margolin

unread,
Feb 10, 1999, 3:00:00 AM2/10/99
to
In article <36C20446...@IntelliMarket.Com>,

Kelly Murray <k...@IntelliMarket.Com> wrote:
>I suppose I'll get crucified (yet again) on syntax to
>improve the language...
>
>Macrolet and flet are very useful,
>but I find the syntax is an excessive burden.
>I suggest they can just be defined by embedded definition
>at the beginning of a code body. Seems to me a simple
>transformation in which similiar processing is already
>done to handle type declarations.

One difference is that type declarations don't introduce new bindings. If
things were done "right" type declarations would actually be part of the
syntax of the variable specification in a LET form, rather than being
shoved down into the beginning of the body (see how Dylan does it, for
example).

On the other hand, Scheme has something like what you're suggesting.
"define" forms at the beginning of a function body are transformed into a
"letrec" form surrounding the body. So there certainly is precedent in the
Lisp family for this type of syntax. However, I think many Schemers
consider that syntax a wart in the language. My suspicion is that it's
there to provide a way to elide the "lambda", i.e.

(define (foo ...)
(letrec ((inner
(lambda (...)
...)))
...)) =>

(define (foo ...)
(define (inner ...)
...)
...)

It saves both a level of indentation *and* a lambda.

--
Barry Margolin, bar...@bbnplanet.com
GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Don't bother cc'ing followups to me.

Dorai Sitaram

unread,
Feb 10, 1999, 3:00:00 AM2/10/99
to
In article <tEnw2.2136$oD6....@burlma1-snr1.gtei.net>,

Barry Margolin <bar...@bbnplanet.com> wrote:
>
>On the other hand, Scheme has something like what you're suggesting.
>"define" forms at the beginning of a function body are transformed into a
>"letrec" form surrounding the body. So there certainly is precedent in the
>Lisp family for this type of syntax. However, I think many Schemers
>consider that syntax a wart in the language. My suspicion is that it's
>there to provide a way to elide the "lambda", i.e.
>
>(define (foo ...)
> (letrec ((inner
> (lambda (...)
> ...)))
> ...)) =>
>
>(define (foo ...)
> (define (inner ...)
> ...)
> ...)
>
>It saves both a level of indentation *and* a lambda.

There is a rationalization for this wart, but I don't
know if it is aggressively voiced. To wit, both the
top-level and the internal DEFINE can be viewed as
mutating the current top-most frame of the environment,
without pushing on a new frame. The LETREC, on the
other hand, adds a brand new frame of bindings, which
can be popped off. E.g., in

(define (foo ...)
(letrec ((inner (lambda ...)))
...))

one could in theory insert code between the last two
right-parens that is still within FOO but won't see
INNER. This wouldn't be possible if we'd used

(define (foo ...)
(define (inner ...) ...)
...)

I believe people are unwilling to retire the internal
DEFINE because its presence (even if its use is
avoided) gives a consistent meaning to DEFINE. Making
DEFINE top-level only would make DEFINE too special.
And Schemers hate special (!). Besides, there is a
possibility that an eventual module or first-class
environment system might make the notion of an
exclusively top-level DEFINE untenable. Every DEFINE
would then be an internal DEFINE.

--d


paul_...@scientia.com

unread,
Feb 11, 1999, 3:00:00 AM2/11/99
to
In article <36C20446...@IntelliMarket.Com>,
Kelly Murray <k...@IntelliMarket.Com> wrote:
> I suppose I'll get crucified (yet again) on syntax to
> improve the language...
>
> Macrolet and flet are very useful,
> but I find the syntax is an excessive burden.
> I suggest they can just be defined by embedded definition
> at the beginning of a code body. Seems to me a simple
> transformation in which similiar processing is already
> done to handle type declarations.
>
> -Kelly Murray k...@intellimarket.com
>
> (function foo (x y)
> (macro stroke (s) ...)
> (function length (s) ...)
> ...
> (stroke (length 10))
> )
>
> As compared with the code below, which is difficult
> to convert from having local defs to not having them,
> as well as adding and deleting individual defs.
>
> (defun foo (x y)
> (macrolet (
> (stroke (s) ...)
> )
> (flet (
> (length (s) ...)
> )
> ...
> (stroke (length 10))
> )
> )
> )
>

You could write a macro, say local-definitions, such that you could
do something like:

(defun foo (x y)
(local-definitions
((macro stroke (s) ...)
(function length (s) ...)
(variable z ...)
....)
...
(stroke (length 10))))

which then expanded into the corresponding macrolet/flet/let forms

This isn't quite what you asked for, but goes at least part of the way.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

Marco Antoniotti

unread,
Feb 11, 1999, 3:00:00 AM2/11/99
to

paul_...@scientia.com writes:

This is equivalent to the request of interspersed 'variable
declarations' which was made on CLL some time ago.

The easy part is to provide a WITH- form that does the one level down
parsing and rewriting. (I wrote the VARDEF package just for the heck
of it).

However, to do the "right" (quotes necessary) you need a code walker

Cheers

--
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - (0)6 - 68 10 03 17, fax. +39 - (0)6 - 68 80 79 26
http://www.parades.rm.cnr.it

Chris Reedy

unread,
Feb 11, 1999, 3:00:00 AM2/11/99
to
Marco Antoniotti wrote:
>
> paul_...@scientia.com writes:
>
> > ...
> > You could write a macro, say local-definitions, such that you could
> > do something like:
> >
> > (defun foo (x y)
> > (local-definitions
> > ((macro stroke (s) ...)
> > (function length (s) ...)
> > (variable z ...)
> > ....)
> > ...
> > (stroke (length 10))))
> >
> > which then expanded into the corresponding macrolet/flet/let forms
>
> This is equivalent to the request of interspersed 'variable
> declarations' which was made on CLL some time ago.
>
> The easy part is to provide a WITH- form that does the one level down
> parsing and rewriting. (I wrote the VARDEF package just for the heck
> of it).
>
> However, to do the "right" (quotes necessary) you need a code walker
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

(Probably a dumb question) Why do you need a code walker?

Thanks, Chris

Tom Breton

unread,
Feb 12, 1999, 3:00:00 AM2/12/99
to
Chris Reedy <cre...@mitretek.org> writes:

> Marco Antoniotti wrote:
> >
> > paul_...@scientia.com writes:
> >
> >
> > This is equivalent to the request of interspersed 'variable
> > declarations' which was made on CLL some time ago.

Which I posted on gnu.emacs.sources, since people seem to have
forgotten about it. I use it regularly, and it is the handiest thing.
I'm glad I wrote it, and will repost it if you can't find it.

> >
> > However, to do the "right" (quotes necessary) you need a code walker
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> (Probably a dumb question) Why do you need a code walker?

Because he wants to manage not just second-level structures but
nth-level structures.


--
Tom Breton, http://world.std.com/~tob
Ugh-free Spelling (no "gh") http://world.std.com/~tob/ugh-free.html

Marco Antoniotti

unread,
Feb 12, 1999, 3:00:00 AM2/12/99
to

Tom Breton <t...@world.std.com> writes:

> Chris Reedy <cre...@mitretek.org> writes:
>
> > Marco Antoniotti wrote:
> > >
> > > paul_...@scientia.com writes:
> > >
> > >
> > > This is equivalent to the request of interspersed 'variable
> > > declarations' which was made on CLL some time ago.
>
> Which I posted on gnu.emacs.sources, since people seem to have
> forgotten about it. I use it regularly, and it is the handiest thing.
> I'm glad I wrote it, and will repost it if you can't find it.
>
> > >
> > > However, to do the "right" (quotes necessary) you need a code walker
> > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >
> > (Probably a dumb question) Why do you need a code walker?
>
> Because he wants to manage not just second-level structures but
> nth-level structures.
>

Exactly.

(define-function foo (x)
(let ((y (f x)))
(define-function bar (z) (+ x y))
#'bar))

I believe Scheme gets it right. Extending CL with a code walker is
trivial.

Kelly Murray

unread,
Feb 12, 1999, 3:00:00 AM2/12/99
to
Introducing new variables without an embedded syntax is similiar,
but in terms of syntax I think there is a significant difference
between the two, because macros/functions have parens as
part of their definition already, and one tends to introduce
many variables as in (NiCLOS syntax:)
(let x = 10
y = 20
z = 30
do
..)

However, I can see how it would make sense, even if it does
add more paren to the variable declarations:

(function foo (x)
(var x 10) ;; local bindings
(var y 20)
(var z 30)
(function zap (s)(+ s 1)) ;; local function zap
;; start of actually executable code
(zap y)
)

I think the problem is that one really also needs another
single-expression syntax for embedded use, so it makes
the syntax inconsistent and thus harder to understand the code.

(function foo (x)
(var x 10) ;; local bindings
(function zap (s)(+ s 1)) ;; local function zap
;; start of actually executable code
(if (> x 10)
(progn ;; progn is just a replacement for let
(var x (- x 10)) ;; bind locally
(zap x))
)

Local variable/functions are rarely used much in an
embedded context, so this type of problem wouldn't be common.

But in any case, I then rethink my suggestion,
and revise it so function/macro definitions do use embeddings,
but change the syntax to be less cumbersome, similiar
to what Paul Rudin suggested:
I believe this is much cleaner and easier to understand
and is more consistent with my other syntax.

(function foo (x)
(flet
(function zap (s) (+ s 1))
(macro zip (s) `(zap (- ,s 1)))
do ;; clear start-code indication
(zap y)
)
)


-Kelly Murray k...@niclos.com (notice new domain ;)

Tom Breton wrote:
>
> Chris Reedy <cre...@mitretek.org> writes:
>
> > Marco Antoniotti wrote:
> > >
> > > paul_...@scientia.com writes:
> > >
> > >
> > > This is equivalent to the request of interspersed 'variable
> > > declarations' which was made on CLL some time ago.
>
> Which I posted on gnu.emacs.sources, since people seem to have
> forgotten about it. I use it regularly, and it is the handiest thing.
> I'm glad I wrote it, and will repost it if you can't find it.
>
> > >
> > > However, to do the "right" (quotes necessary) you need a code walker
> > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >
> > (Probably a dumb question) Why do you need a code walker?
>
> Because he wants to manage not just second-level structures but
> nth-level structures.
>

Paul Rudin

unread,
Feb 13, 1999, 3:00:00 AM2/13/99
to
paul_...@scientia.com writes:


>
> You could write a macro, say local-definitions, such that you could
> do something like:
>
> (defun foo (x y)
> (local-definitions
> ((macro stroke (s) ...)
> (function length (s) ...)
> (variable z ...)
> ....)
> ...
> (stroke (length 10))))
>
> which then expanded into the corresponding macrolet/flet/let forms
>

> This isn't quite what you asked for, but goes at least part of the way.
>

And if you've got this you could then do another macro, say
defun-with-locals, so that you could write

(defun-with-locals foo (x y)


(macro stroke (s) ...)
(function length (s) ...)

...
(stroke (length 10))
)

with expands to the corresponding defun and local-definitions forms,
which gets you nearly all the way.


Kent M Pitman

unread,
Feb 13, 1999, 3:00:00 AM2/13/99
to
I've been following this a bit, but only spot-checking. Not following
detailed discussiones. Apologies in advance if I repeat something someone
has said...

I don't have a problem with people discussing this, and it's interesting
to see what people have as wishes, but you're all using such simple examples
that they don't address the full scope of things.

I've said over and over that design features are not "good" or "bad"
but "good in a context" or "bad in a context", so to fairly evaluate
(and possibly repair) CL's local definitions, you have to first acknowledge
the full space of issues that CL is trying to deal with. Saying you only
use a couple of parts of the language and so a simple analysis suffices
doesn't really attack the whole problem.

Here are some additional things to puzzle over that may, if not make
you happy to use MACROLET and FLET, at least help you understand why
they were probably chosen over top-level DEFMACRO in a form:

[1] (defun foo (x) (+ x 1))
(defun bar (&optional (x (foo x)))
(defun foo (x) (- x 1))
(foo x))

Sure, you could define this either way--with the initform's FOO
using either the outer FOO or the inner FOO, but I can just
about guarantee you that we'd be answering questions here on
comp.lang.lisp about the scoping rules, just as we do about
declaration scope.

A variation on this problem is
(defun bar (&optional (x (foo x)))
(progn (defun foo (x) (- x 1)))
(foo x))
which is important for abstraction purposes since at top-level,
PROGN doesn't destroy top-level-ness, and you'd want a defining
macro to expand into a context.

[2] (defun setup-adder (x)
(defun adder (n) (+ n x)))

This function would become harder to write. Yes, you could change
it to
(defun setup-adder (x)
(setf (symbol-function 'adder) #'(lambda (n) (+ n x))))
but that would force people to have a lot larger repertoire about
the various parts of the language.

[3] Already many people do
(LET ((X 3))
(DEFUN F () X)
(DEFUN NEW-F () (INCF X)))
to hide use of special variables. To keep this working, you have
to either define that this is not the same as
((LAMBDA (X)
(DEFUN F () X)
(DEFUN NEW-F () (INCF X)))
3)
or that this lambda combination is not the same as
(DEFUN SETUP-F (X)
(DEFUN F () X)
(DEFUN NEW-F () (INCF X)))
(SETUP-F 3)
Right now, people assume those three renditions are all the same.

(Oops. Sorry abou thte uppercase, I did that by accident. Well,
not tha sorry. I often program in uppercase, but today I'm not
going to downcase it because it's probably good to sometimes be on
the record that some of us like uppercase just for no other reason
than that we like it. I feel like a closeted minority always in
danger of extinction.)

I think these cases make me comfortable that while indentation
is sad, it's not as sad as those questions up there. People rarely
ask what the scope of FOO is in
(defun bar (&optional (x (foo x)))
(flet ((foo (x) (- x 1)))
(foo x)))
nor in
(flet ((foo (x) (- x 1)))
(defun bar (&optional (x (foo x)))
(foo x)))
because it's explicit. A few people DO come in with the "BASIC bug"
of thinking LET is an assignment statement and wanting to write
(LET ((X 3)))
in order to assign X. But that bug is more easily fixed than [1].

Incidentally, [3] is really a combination of the problems I cited
in [1] and [2] because [1] is isomorphic to the problems we already
have with declarations (which some have leaned toward fixing with
more "binding forms", not less) and [2] is just a simpler form of [3]
where you don't plan to do the setup more than once so you didn't
name the function. But the reason I mention it is that in [1] we also
had a historical problem that we were forced by the declaration model
we chose to break an age-old tradition of LET and LAMBDA having the
comfortable relationship see in [3]. In Maclisp, LET could literally
be defined by the following (at least, it could toward the end of the
maclisp era when backquote, sharpsign, and defmacro finally made it into
the language after years of being user-defined extras):
(defmacro let (bindings &body forms)
`((lambda ,(mapcar #'car bindings) ,@forms)
,@(mapcar #'cadr bindings)))
But in CLTL1, this didn't work because the scoping rules of declarations
were such that you had to parse the FORMS argument for declarations, and
some of those declarations belonged in the body of the lambda while
others belonged in the evaluation of the arguments. (It was statistically
a better guess to that all declarations were better off being replicated
in both pieces, but I vaguely recall there was a screw case to that,
perhaps related to this goofy notion of "bound" and "free" declarations.)
Anyway, the point here is that if you introduce top-level DEFUN as a way
of "declaring" a local function, you implicitly also introduce another
confusion of this type, where when the DEFUN into which the local DEFUN
is placed is broken down into its component pieces, the mechanism to do
that gets very complicated.

Trends in Eulisp in the last decade were toward replacing CL-ish things
like
(defun f (x)
(declare (special x))
...)
with
(defun f (y)
(dynamic-let ((x y))
...))
so that it was explicit what the scope of the specialness was, I suspect
at least partly for the reasons I've cited.

All of this being said, nothing keeps anyone here from contributing a library
that has a theory of top-level-ness such that you can do DEFUN or whatever
you think belongs at top-level. I recommend that you NOT re-do the mistake
I talked the CL designers into making for CLTL1 and that was removed in CLTL2
where you macroexpand forms looking for declarations. There are two reasons
for this. First, it can easily get you into really bad performance problems
in nested situations without a strong theory of caching. That's not
very relevant here. But second, if macros can expand into setup code,
then
(defmacro define-frob (x)
`(progn (defun ,x () 'frob)
(push ',x *frobs*)
',x))
will have odd behavior if you nest it, as in:
(my-defun foo ()
(define-frob fred)
...)
where you've defined my-defun to turn body-embedded defuns into flets
by macroexpanding to find them. The problem is you'll end up with:
(defun foo ()
(flet ((fred () 'frob))
(push 'fred *frobs*)
'fred
...))
which will be a problem becuase even though you've scoped the FRED
functional definition, you've not scoped its "registration".
It's true, if the binding rules for the language were such that all
definitions did this local function hack, people might not define
frobs in that way--they might have a more complex way to do it.

Once in a while, I get namespace-happy and put all my extra functions
in FLETs and sit there proud that I've not cluttered my package's set
of symbols, but then I usually think "what point was there in that?
the package was all mine and I have nothing else to put there".
And when I look at the set of local functions that I really need, I often find
most of them (for efficiency, because I don't want to cons) can trivially
be floated out to toplevel as "loop invariants". And in the end, I mostly
don't see a lot of local functions. I use them, but rarely. And for as
often as I do, I don't mind the extra indentation.

What I DO mind is that no one ever ported the hack that made emacs
c-m-q in older editors indent things like:
(flet ((foo (x)
foobody))
fletbody)
I'm forever having to manually reindent these. But the fix is not
a fix to the language. That's a tools problem.

End of morning rant. Time to scout out some breakfast.


Kelly Murray

unread,
Feb 16, 1999, 3:00:00 AM2/16/99
to
I only spot-checked Kent response ;) but thought I'd add what
is probably obvious about local definitions:

1) You know for certain the definition is not used elsewhere,
and thus can change it at will.
2) The definition is visibly connected where it is used,
important when editing "by form" instead of "by file".
3) It makes any function redefinition as a unit,
useful for transactional editing.
4) Creating another unneeded symbol (as another root-set entry)
is avoided
5) The compiler may optimize the call

The downside is mostly that it is harder to trace the sub-call,
which wouldn't be the case if we had source-code stepping..

0 new messages