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

Bad Idiom?

19 views
Skip to first unread message

Will Hartung

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

Hi All!

Spending some precious moments away from Baywatch, I was playing about
with some code. A function that does list traversal and changes as we
go.

(defun func (x)
(let* ((func-helper #'(lambda (y)
(if (not (null y))
(cons (1+ (car y))
(func-helper (cdr y)))))))
(func-helper x)))

Now, in Scheme, we have (letrec ...) which does these kinds of
structures just peachy. But in ACL Lite for Windows, I get a warning
that FUNC-HELPER is not defined and will be treated as dynamic.
Technically, this shouldn't be an issue at runtime, because by the
time we get around to needing FUNC-HELPER, it will, indeed, be bound to
my little lambda.

In most Scheme texts, this kind of list traversing idiom seems pretty
common. The authors really love this stuff. And I'm generally fond of
it as well, save for my code gets pretty deeply nested and starts to
look bad even to the pretty-printer.

Is this the common idiom for list traversal in CL? Is their some other
CL bit that I (somehow) managed to miss that sort of automates this
kind of process? (do ...) is popular, but not for lists. Maybe
something with (loop ...) or (for ...) or who knows.

The CL iteration facilities may be rich, but I find them a touch
difficult grab a hold of. They're a far cry from FOR i= 1 to 100. You
may be able to DO this easily, but have you ever seen the help page in
ACL regarding FOR (see loop) and LOOP (...ummmm....its long!).

Of course, this is the story of my LISP life, it seems.

Anyway, since ACL complained, I'm wont to think that my little
function is Bad Form. Idiomatically Challenged.

So, I'm hoping for what Real Lisp programmers do, so I can pick up
more Lisp Idioms and therefore become a better, and more knowledgable
Lisp Idiot! :-)

Thanx for any help!

--
Will Hartung - Rancho Santa Margarita. It's a dry heat. vfr...@netcom.com
1990 VFR750 - VFR=Very Red "Ho, HaHa, Dodge, Parry, Spin, HA! THRUST!"
1993 Explorer - Cage? Hell, it's a prison. -D. Duck

Barry Margolin

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

In article <vfr750E...@netcom.com>,
Will Hartung <vfr...@netcom.com> wrote:
>Hi All!

>(defun func (x)
> (let* ((func-helper #'(lambda (y)
> (if (not (null y))
> (cons (1+ (car y))
> (func-helper (cdr y)))))))
> (func-helper x)))

In Common Lisp, LET and LET* bind in the variable namespace, but you're
looking up FUNC-HELPER in the function namespace. You need to use

(funcall func-helper ...)

>Now, in Scheme, we have (letrec ...) which does these kinds of
>structures just peachy. But in ACL Lite for Windows, I get a warning
>that FUNC-HELPER is not defined and will be treated as dynamic.

For local function definitions, Common Lisp has FLET and LABELS, which make
local bindings in the function namespace. LABELS is like Scheme's letrec,
in that the bindings are visible in the bodies of the functions. So the
above would be written:

(defun func (x)
(labels ((func-helper (y)


(if (not (null y))
(cons (1+ (car y))
(func-helper (cdr y))))))

(func-helper x)))

>Technically, this shouldn't be an issue at runtime, because by the
>time we get around to needing FUNC-HELPER, it will, indeed, be bound to
>my little lambda.

No, because of the namespace problem. If it would have been bound, the
compiler wouldn't have warned.
--
Barry Margolin
BBN Planet, Cambridge, MA
bar...@bbnplanet.com - Phone (617) 873-3126 - Fax (617) 873-5508
(BBN customers, please call (800) 632-7638 option 1 for support)

Mark McConnell

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to Will Hartung, vfr...@netcom.com

> (defun func (x)
> (let* ((func-helper #'(lambda (y)
> (if (not (null y))
> (cons (1+ (car y))
> (func-helper (cdr y)))))))
> (func-helper x)))

(1) In Scheme, a variable [here "func-helper"] has only one kind of
value. If you call (c x), and the value of c is a function object,
then Scheme applies the function to the value of x, which is what
you want.

In Common Lisp, a variable has several "kinds" of values, the two
main ones being the ordinary value and the function value [I forget
the correct technical terms]. (defun c (x) ...) stores a function
object in the _function value_ of c, while let, setq, and so on
stores things into the _ordinary value_. In your code,
you have used let to store a function object #'(lambda ...) into
the _ordinary_
value. The call (func-helper x) makes the system look in the
function value of func-helper; there is nothing in the function
value, so the system reports an error.

(2) You don't really need the helper. The following code does the
same thing, in the same style.

(defun func (x)
(if (not (null x))
(cons (1+ (car x))
(func (cdr x)))))

(3) The code in (2) has a problem: if N is the length of x, the code
forces the system to remember N function calls before it returns
any values at all. For instance,

(func '(1 2 3 4 5)) becomes
(cons 2 (func '(2 3 4 5)) becomes
(cons 2 (cons 3 (func '(3 4 5))) ...

Notice how the lines get longer, and how the last line will be
a call to N cons's all in a row. None of the conses can be performed
until the row of N is completed. I have seen otherwise reasonable
Lisps choke on this when N is, say, 100 or 1000.

Here is a "tail-recursive" way to write your function. A good
compiler will treat this more efficiently, because the cons's
are performed as you go along. A helper function is necessary
here, because you use a 2nd argument to hold the answer.

(defun func (x)
(func-aux x '()))

(defun func-aux (x answer)
;; Assumes x is a list of numbers.
(cond ((null x)
(nreverse answer))
(t
(func-aux (cdr x) (cons (1+ (car x)) answer)))))

(4) Above all, as a previous poster said, the best
way to write this function is

(mapcar #'1+ x)

Francis Leboutte

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

vfr...@netcom.com (Will Hartung) wrote:

>...


>Spending some precious moments away from Baywatch, I was playing about
>with some code. A function that does list traversal and changes as we
>go.
>

>(defun func (x)
> (let* ((func-helper #'(lambda (y)
> (if (not (null y))
> (cons (1+ (car y))
> (func-helper (cdr y)))))))
> (func-helper x)))
>

>Now, in Scheme, we have (letrec ...) which does these kinds of
>structures just peachy. But in ACL Lite for Windows, I get a warning
>that FUNC-HELPER is not defined and will be treated as dynamic.

>Technically, this shouldn't be an issue at runtime, because by the
>time we get around to needing FUNC-HELPER, it will, indeed, be bound to
>my little lambda.

Use labels to define local named recursive functions :

(defun func (x)
(labels ((func-helper (y)


(if (not (null y))
(cons (1+ (car y))
(func-helper (cdr y))))))

(func-helper x)))


>The CL iteration facilities may be rich, but I find them a touch
>difficult grab a hold of. They're a far cry from FOR i= 1 to 100. You
>may be able to DO this easily, but have you ever seen the help page in
>ACL regarding FOR (see loop) and LOOP (...ummmm....its long!).

>...

The for loop is quite unlispy and generally not well explained.

(loop for i from 0 below 10
do
(print i))

You could define your own for macro:

(defmacro for- ((var start stop) &body body)
(let ((gstop (gensym)))
`(do ((,var ,start (1+ ,var))
(,gstop ,stop))
((= ,var ,gstop))
,@body)))

(for- (i 0 10))

For details about macros you may have a look in the book of P.Graham, On
Lisp, Adavanced Techniques for Common Lisp (Prentice Hall). ANSI Common
Lisp (From Graham too, same editor) is a good starting point for CL (CLOS
included).

Francis

--
Francis Leboutte, Algorithme, Rue de la Charrette 141, 4130 Tilff, Belgium
al...@glo.be http://user.glo.be/~algo t&fax: +32-(0)4-3883528

Erik Naggum

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

* Will Hartung
| I know that the function I presented works suspiciously like MAPCAR
| (Thanx Rainer!), but I was trying to understand how the "helper function"
| idiom translated from Scheme to Lisp.

I have seen that idiom used much less in Common Lisp than in Scheme. I
believe the main reason for this is that Scheme provides neither packages
nor separate namespaces for functions and variables, so a helper function
would pollute the only namespace you have if it was not lexically enclosed.

however, the main reason I think helper functions are used in Scheme is
that binding a function to a different symbol (name, really) tends to wreak
havoc with explicit recursion. by "explicit recursion" I mean that a
function calls itself by name. a lexically enclosed helper function allows
a function to call a function which is known by name, even though the
calling function may not know its _own_ name. Scheme-lovers argue that
functions are "more first-class" in Scheme than in Common Lisp. I think
this idiom is a consequence of that, namely that functions can end up being
assigned to any varabiel and thus called any which way.

| But would a Compiler make a more efficient construct using LOOP than a
| similiar tail-recursive technique? I would imagine that something that is
| (somewhat) intuitive in a recursive function may not translate to well to
| the LOOP macro.

depends on the compiler. I asked about a similar question some time ago
(whether (map nil <function> <list>) and (dolist (elt <list>) (<function>
elt)) would compile to the same code), and the answers I received uniformly
supported my assumption that this should be possible, indeed common.

| I guess the real question is whether the LOOP macro is worth studying, or
| should I just skip it and rely on the other contructs and techniques that
| I've learned from Scheme and the simpler functions available in Lisp?

yes, the loop macro is worth studying.

| It's difficult at this point because I feel like I'm walking on eggs. I
| question everything I'm doing. Am I doing the right thing? Is this the
| best way? CL is so big, I feel like I'm always missing something.
| Perhaps I should just shut up, write my little projects and then find
| some soul willing to kibitz the code after it works.

I felt that way with C++ (which is why I took up Common Lisp for real).
however, in C++, your program will crash or unexpectedly fail in more
subtle ways if you don't include the right incantations. in Common Lisp,
there is nothing that can have such adverserial effects, so you're always
doing the right thing (insofar as you know what you're doing at all :),
even if you don't do it the best way. this is the joy of learning I find
with Common Lisp, namely that I _don't_ have to know the entire language
before I can start doing something productive in it. the same goes for
Franz, Inc's Allegro Common Lisp. I'm using both the Windows and the Unix
versions and they have about 1200 pages of user manuals combined. I have
fond it extremely instructive to read manuals from cover to cover, not the
least because what makes a difference in Lisp systems is what is offered in
addition to the Common Lisp compiler and interpreter. however, at no point
did I feel that if I did something less than optimal, it would crash on me
or lose my work. in my view, this is quite different a feeling from what
one is taught to expect in the C/C++/Unix world. I don't know if Scheme is
similarly pavlovian in its effects on programmers who make mistakes.

| But working with CL is like walking into the local warehouse hardware
| store looking for a hammer, and upon entering the tool section, not only
| do I find a WALL of hammers, but walls of everything else as well.

but in C++, they are all wired to the 110V mains outlet, so if you pick up
the hammers in the wrong order, you get seriously fried. the Common Lisp
hammers are all just hammers, no evil included.

#\Erik
--
1,3,7-trimethylxanthine -- a basic ingredient in quality software.

Will Hartung

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

vfr...@netcom.com (Will Hartung) writes:

(Hey! That's ME! What's HE doing here??)

[ Previous post snipped for brevity ]

Wow! You think its bad here, you should see my mail!

Anyway, thanx all for pointing the way with your posts and e-mail.

I think (I need to look at it a bit more) that LABELS is what I was
looking for. LABELS makes the could look much cleaner. I thought the
routine looked rather ugly. Besides, it didn't even work! (Worked
great for ONE item :-)).

I know that the function I presented works suspiciously like MAPCAR
(Thanx Rainer!), but I was trying to understand how the "helper
function" idiom translated from Scheme to Lisp.

I use that concept all over the place in Scheme stuff.

I noticed that in ANSI Common Lisp that Paul writes two functions
(compress and compr, compr being the "helper" of compress) whereas in
most Scheme texts, they seem to bundle them together. I'm assuming
that Paul didn't bundle them together because he'd have to introduce
LABELS and FLET much earlier for this example.

Recursive functions are great at building and tearing apart lists, but
I have to wonder if one of the iteration constructs is also useful for
building lists? I'm guessing that LOOP might be able to handle it, Paul
spends 3 pages describing LOOP in his CL reference, and its pretty
clearly a Quick Reference.

But would a Compiler make a more efficient construct using LOOP than a
similiar tail-recursive technique? I would imagine that something that
is (somewhat) intuitive in a recursive function may not translate to
well to the LOOP macro.

I guess the real question is whether the LOOP macro is worth studying,


or should I just skip it and rely on the other contructs and
techniques that I've learned from Scheme and the simpler functions
available in Lisp?

It's difficult at this point because I feel like I'm walking on eggs. I


question everything I'm doing. Am I doing the right thing? Is this the
best way? CL is so big, I feel like I'm always missing something.
Perhaps I should just shut up, write my little projects and then find
some soul willing to kibitz the code after it works.

But working with CL is like walking into the local warehouse hardware


store looking for a hammer, and upon entering the tool section, not only
do I find a WALL of hammers, but walls of everything else as well.

"Everyday it's the same thing. Variety! I want something different."

But thanx again everyone for your responses. I really appreciate it
all, its very helpful.

Barry Margolin

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

In article <vfr750E3...@netcom.com>,

Will Hartung <vfr...@netcom.com> wrote:
>But would a Compiler make a more efficient construct using LOOP than a
>similiar tail-recursive technique? I would imagine that something that

Some will, some won't. Not all CL compilers do tail-recursion optimization
(unlike Scheme, it's not required by the language specification). While I
think there are some compilers that may generate approximately equivalent
code for the two versions, I doubt there are any where the tail recursive
version would generate *more* efficient code. Most LOOP macros generate
code that's very easy to compile efficiently (it generally expands into a
TAGBODY with lots of GO statements -- the control structure is probably
very close to the resulting generated code).

Note, by the way, that your example function was not tail-recursive,
because the result of the recursion was being used as a parameter to
another function. Tail calls can only be optimized into goto's if the
result of the call is being used as the return value of this function.

>is (somewhat) intuitive in a recursive function may not translate to
>well to the LOOP macro.

That's true, you often need to rethink the algorithm a little.

>I guess the real question is whether the LOOP macro is worth studying,
>or should I just skip it and rely on the other contructs and
>techniques that I've learned from Scheme and the simpler functions
>available in Lisp?

Others may disagree, but I think LOOP is worth knowing, but you don't need
to know all its intricacies. There are a few things that can be done
extremely conveniently with LOOP, and I will usually use LOOP in those
cases. In particular, the COLLECTING feature is one of the most
convenient.

The Generators and Collectors macros described in Appendix B of CLtL2 also
provide this convenience and are much more Lisp-like. It's too bad they
weren't around when LOOP was gaining popularity, and I think they're a
better way to go. But LOOP is what I got used to, and its popularity is
why it got elevated into the ANSI standard.

Rainer Joswig

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

In article <vfr750E...@netcom.com>, vfr...@netcom.com (Will Hartung) wrote:

> Spending some precious moments away from Baywatch, I was playing about
> with some code. A function that does list traversal and changes as we
> go.
>
> (defun func (x)
> (let* ((func-helper #'(lambda (y)
> (if (not (null y))
> (cons (1+ (car y))
> (func-helper (cdr y)))))))
> (func-helper x)))

; This is your example:

(defun func (x)
(let* ((func-helper #'(lambda (y)
(if (not (null y))
(cons (1+ (car y))
(func-helper (cdr y)))))))
(func-helper x)))


;If you need a recursive function, the local function
;in your example is not necessary. Also you may want
;to use FIRST instead of CAR.

(defun func (x)
(if (null x)
nil
(cons (1+ (first x)) (func (rest x)))))

(func '(1 2 3)) ; (2 3 4)


;Now you have 1+ hard coded into the function. Extract it.
;Rename the variables to make the code more self documenting.

(defun func (function sequence)
(if (null sequence)
nil
(cons (funcall function (first sequence))
(func function (rest sequence)))))

(func #'1+ '(1 2 3))

;Rename func. It really is MAPCAR. We use MY-MAPCAR.


(defun my-mapcar (function sequence)
(if (null sequence)
nil
(cons (funcall function (first sequence))
(my-mapcar function (rest sequence)))))

(my-mapcar #'1+ '(1 2 3))

(mapcar #'1+ '(1 2 3))


; Using loop:

(defun my-mapcar (function sequence)
(loop for item in sequence
collect (funcall function item)))

(my-mapcar #'1+ '(1 2 3))


> The CL iteration facilities may be rich, but I find them a touch
> difficult grab a hold of. They're a far cry from FOR i= 1 to 100. You
> may be able to DO this easily, but have you ever seen the help page in
> ACL regarding FOR (see loop) and LOOP (...ummmm....its long!).

Read Winston/Horn Lisp 3rd Edition. Then Graham ANSI Common Lisp.
Then read Norvig/PAIP and Graham/On Lisp. Then read the Meta Object
Protocol book.

Mean while read some Lisp code (before breakfast).

Rainer Joswig

Jeff Shrager

unread,
Jan 8, 1997, 3:00:00 AM1/8/97
to

Will,

Unless you are trying to do something much more complex than it
appears, which may be given the weirdness of the code you've written,
I think that you are looking for MAPCAR, as:

(mapcar #'1+ '(1 2 3 4 5))
=> (2 3 4 5 6)

Was that all you wanted to do or have I missed your needs altogether?

'Jeff

Rainer Joswig

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to

In article <vfr750E3...@netcom.com>, vfr...@netcom.com (Will Hartung)
wrote:

> I guess the real question is whether the LOOP macro is worth studying,


> or should I just skip it and rely on the other contructs and
> techniques that I've learned from Scheme and the simpler functions
> available in Lisp?

The LOOP macro is really ugly. But it works - most of the time
I can write hairy LOOP code intuitively. The better (IHMO)
alternative is the ITERATE macro. But I do use
LOOP, it is relatively easy to use, generates quite
fast code, it is standard, ... But the semantic properties?
If you can, you are most of the time better off to develop
FP code (see SICP), but you have to make sure that
it compiles to efficient code. A functional approach
gives you more reuse.


> It's difficult at this point because I feel like I'm walking on eggs. I
> question everything I'm doing. Am I doing the right thing? Is this the
> best way? CL is so big, I feel like I'm always missing something.
> Perhaps I should just shut up, write my little projects and then find
> some soul willing to kibitz the code after it works.

Play around with Lisp for a while. Write different versions
of your stuff. Compare them (space and time properties). Do
some benchmarking. Write some LOOP expressions. Try
to understand macro expansions of simple LOOP expressions.
Try to add declarations (types, dynamic-extent, optimize, ...).

Norvig's excellent book "Paradigms of Artificial Intelligence
Programming" (every Common Lisp programmer has a copy ;-) )
explains how LOOP works (page 840-852).

> But working with CL is like walking into the local warehouse hardware
> store looking for a hammer, and upon entering the tool section, not only
> do I find a WALL of hammers, but walls of everything else as well.

Well, Common Lisp does not tell you how to program something.
There are often (always?) more than one way to do it.
You have to find your own programming style or
you may need to adopt the style of the project your
are working in (Lisp - Style and Design, is a title
of a book).

You can easily write Fortran-like code (using labels and jumps).
You can easily write Pascal-like code (using defun and defstruct).
You can easily write FP-like code (using LABELS, FUNCTION, LAMBDA, ...).
You can easily write SmallTalk-like code (using DEFCLASS and DEFMETHOD).
You can easily write CLOS-like code (using MI, MC, MOP).

And there are many more paradigms you can use, or easily integrate.
There are a lot of extensions which allow to use things
like constraints, non determinism, first order logic, KL-ONE,
forward chaining rules, persistent objects, pattern matching,
parser generators, ...

If you have a real problem at hand you can use a infrastructure,
that makes the Common Lisp look small. They
called Lisp "AI assembler".

So what is the next point I'm trying to make?

Learn to live with complexity. How do you do that?

For a programming language you have to get an overview of
the features. Pick the one you need. Look at what is available.
You may live without HANDLER-BIND (atleast for a while).
Try to organize the features.

Make the programming environment your friend. Common Lisp
environments are customizable. They even can be integrated
into your software. They even can be your operating system.
Use the online docs. For MCL I wrote a hack where I can
set the cursor on a Common Lisp symbol, press "c-x f"
and the ANSI CL HyperSpec doc for the symbol comes up in my
web browser.

The core of Common Lisp is relatively simple. You need
to understand some data types, evaluation rules, and functions.
But there are some complicated facilities you may
want to use only when you need to (like developing
a domain specific language extension to Common Lisp).
Does anyone know how to use DEFINE-SETF-METHOD without
looking into the manual? Not many could claim that, I guess.
But looking into the manual is allowed. ;-)


Rainer Joswig

Barry Margolin

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to

In article <joswig-ya0231800...@news.lavielle.com>,

Rainer Joswig <jos...@lavielle.com> wrote:
> But I do use
>LOOP, it is relatively easy to use, generates quite
>fast code, it is standard, ... But the semantic properties?

While the semantics of LOOP are not fully defined (we simply didn't have
time to resolve all the ambiguities when writing the standard -- like a
large program, standards also have to be released with known bugs), 99% of
the time you won't get bitten by the holes. You have to write some pretty
contorted code to run into most of them (they generally involve using the
same variable in multiple places, or some interactions between preamble and
iterator code).

IMHO, any LOOP use that's more than about a half dozen lines long is
probably too complex. As I said in my other posting, LOOP is most useful
when you're using one or two of its built-in features that doesn't exist in
any other CL constructs. I wouldn't ever write

(loop for x in list collect (f x))

since this is better written as (mapcar #'f list). But I would write

(loop for i from 1 to n collect (f i))

because there's no other concise way to express this. And you don't have
to be a language lawyer to understand the semantics of this.

Dean T Allemang

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to


In response to this question, many posters have suggested:

> (4) Above all, as a previous poster said, the best
> way to write this function is
>
> (mapcar #'1+ x)

which is probably the shortest solution. On the other hand, the
original poster (and many others) complained about the difficulty in
writing/reading solutions done with the various CL looping
facilities. The solution to this problem is just

(loop for item in x collect (1+ item))

A bit longer than the mapcar solution, but much more readable (in that
fortran sense) to non-lispers.

Most lispers I know seem very reluctant to use the loop macro, despite
the fact that it is documented in CLTL2, and the chapter there is
quite readable. Is there a reason for this, other than simply it
looks "un-lispy"?

Dean

--
Dean Allemang
Organon Motives, Inc.


Frank A. Adrian

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to


Barry Margolin <bar...@tools.bbnplanet.com> wrote in article
<5b1gv7$q...@tools.bbnplanet.com>...


> In article <vfr750E3...@netcom.com>,
> Will Hartung <vfr...@netcom.com> wrote:
> >But would a Compiler make a more efficient construct using LOOP than a
> >similiar tail-recursive technique? I would imagine that something that
>
> Some will, some won't. Not all CL compilers do tail-recursion
optimization
> (unlike Scheme, it's not required by the language specification). While
I
> think there are some compilers that may generate approximately equivalent
> code for the two versions, I doubt there are any where the tail recursive
> version would generate *more* efficient code. Most LOOP macros generate
> code that's very easy to compile efficiently (it generally expands into a
> TAGBODY with lots of GO statements -- the control structure is probably
> very close to the resulting generated code).
>

I agree with the above, as far as it goes, but don't most modern compilers
then
convert the TAGBODY and GO code into a continuation passing style where any
difference between it and the tail call version be moot? Or do most CL
compilers
still build basic block graphs directly from procedural flow primitives
(like TAGBODY
and GO) to generate code?

I know that this depends on the compiler, I'm just asking for the general
case (if
there is any)...

Frank A. Adrian
frank_...@firstdatabank.com

The opinions expressed above are not necessarily those of First DataBank or
its
parent company - they are, however, the correct ones...

Howard R. Stearns

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to geo...@concentra.com, se...@concentra.com

In the spirit of confusing the issue with even more paradigms, does anyone
have any pointers to information about "let-streams" as used in the ICAD
system?

If I remember correctly from 10 years ago, for non-trivial examples on
Allegro and Symbolics, "let-streams" iteration facilities generated code that
was usually faster than each of tail-recursive functions, map-XX, do-XX, and
loop.

The basic concept is that a "let" style binding is used to declaratively
define a number of interacting streams of data which are used during
macro-expansion to generate the most efficient code. I do not know if/how
this is related to series/generators as described in CLtL2.

At one point I remember that someone at ICAD (now Concentra) had an
inclination to release let-streams independently of the ICAD system, but I
don't think anything ever become of it. Perhaps they would consider donating
the source to the CMU library?


Barry Margolin wrote:
>
> In article <vfr750E3...@netcom.com>,
> Will Hartung <vfr...@netcom.com> wrote:
> >But would a Compiler make a more efficient construct using LOOP than a
> >similiar tail-recursive technique? I would imagine that something that
>
> Some will, some won't. Not all CL compilers do tail-recursion optimization
> (unlike Scheme, it's not required by the language specification). While I
> think there are some compilers that may generate approximately equivalent
> code for the two versions, I doubt there are any where the tail recursive
> version would generate *more* efficient code. Most LOOP macros generate
> code that's very easy to compile efficiently (it generally expands into a
> TAGBODY with lots of GO statements -- the control structure is probably
> very close to the resulting generated code).
>

> Note, by the way, that your example function was not tail-recursive,
> because the result of the recursion was being used as a parameter to
> another function. Tail calls can only be optimized into goto's if the
> result of the call is being used as the return value of this function.
>
> >is (somewhat) intuitive in a recursive function may not translate to
> >well to the LOOP macro.
>
> That's true, you often need to rethink the algorithm a little.
>

> >I guess the real question is whether the LOOP macro is worth studying,
> >or should I just skip it and rely on the other contructs and
> >techniques that I've learned from Scheme and the simpler functions
> >available in Lisp?
>

> Others may disagree, but I think LOOP is worth knowing, but you don't need
> to know all its intricacies. There are a few things that can be done
> extremely conveniently with LOOP, and I will usually use LOOP in those
> cases. In particular, the COLLECTING feature is one of the most
> convenient.
>
> The Generators and Collectors macros described in Appendix B of CLtL2 also
> provide this convenience and are much more Lisp-like. It's too bad they
> weren't around when LOOP was gaining popularity, and I think they're a
> better way to go. But LOOP is what I got used to, and its popularity is
> why it got elevated into the ANSI standard.

Riesbeck

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to

In article <5b20mn$r...@tools.bbnplanet.com>, bar...@tools.bbnplanet.com
(Barry Margolin) wrote:

> IMHO, any LOOP use that's more than about a half dozen lines long is
> probably too complex. As I said in my other posting, LOOP is most useful
> when you're using one or two of its built-in features that doesn't exist in
> any other CL constructs. I wouldn't ever write
>
> (loop for x in list collect (f x))
>
> since this is better written as (mapcar #'f list). But I would write
>
> (loop for i from 1 to n collect (f i))
>
> because there's no other concise way to express this. And you don't have
> to be a language lawyer to understand the semantics of this.

Exactly what I teach my AI programming students. No code should be
longer than 6 lines, but especially not LOOP, and *two* LOOP features
tends to be the optimal case.

Another very common situation that's much more readable in
LOOP (IMHO) than alternatives like REMOVE-IF-NOT is

(loop for x in l when (testp x) collect x)

--
-- Chris

Barry Margolin

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to

In article <32D512A8...@elwoodcorp.com>,

Howard R. Stearns <how...@elwoodcorp.com> wrote:
>In the spirit of confusing the issue with even more paradigms, does anyone
>have any pointers to information about "let-streams" as used in the ICAD
>system?

It sounds like you're referring to Dick Waters's LetS package. This was
later enhanced and was renamed "Series". It's described in Appendix A of
CLtL2.

The big difference between LetS and Series is that the latter doesn't
require the let-streams or LetS wrapper macro. The wrapper macro would do
a code walk and find all the operators that were part of it, analyze them
to ensure that they were serializable (and aborts if not) and then
macroexpand into ordinary iteration code. The Series package puts the
smarts in each of the macros, and doesn't require that the resulting code
be serializable; if it is, it will generate optimal code, otherwise it will
generate functions using closures.

Ken Tilton

unread,
Jan 9, 1997, 3:00:00 AM1/9/97
to

Dean T Allemang wrote:
>
> Most lispers I know seem very reluctant to use the loop macro, despite
> the fact that it is documented in CLTL2, and the chapter there is
> quite readable. Is there a reason for this, other than simply it
> looks "un-lispy"?
>

My vote anyway is you are close, except I would say it /is/ unlispy. <g>

Also, when I had a loop macro coded somewhere I saw some message about
extra stuff being loaded (you know, the loop macro <g>) so I got a
perhaps mistaken sense I could reduce image size by sticking to Lisp.

Are there pros for loop I do not know of? I saw a mention that it was
fast. faster than do?

0 new messages