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

LOOP vs DOLIST and friends vs MAPCAR and friends

534 views
Skip to first unread message

christop...@gmail.com

unread,
Jul 29, 2017, 8:02:17 PM7/29/17
to
Hi guys, so I'm new to Common Lisp, having migrated to CL from Racket, and I have a question about looping constructs. I don't intend to start a flamewar, just want some nice pros and cons and general style tips. I would like either: A) the general style of CL or B) an interesting point of view. Here's my general impression:

## The LOOP macro

LOOP is super complex, but also insanely powerful and (from a passing comment read somewhere else) quite efficient. There is no analogue in other languages for the mighty LOOP. Basically, it reads like english, has strange keywords, but can do a TON in one place. For me, as far as I can tell, the Pro/Con chart is like this:

Pros:
- Powerful
- Concise (in most cases)
- Well-documented

Cons:
- Complex (hard to debug, many ways to do things)
- Hard to remember (have to look at the docs every time)
- Ugly (stands out from other parts of the code)
- Verbose (in rare cases)

## MAPCAR and friends

This function and its relatives are favored in more functional languages like Scheme, Haskell, and Clojure. It is relatively concise compared to C-like loops, but not much better than LOOP. They are sometimes wasteful because they lack a way to return early, and there are many variants for various things, instead of a one-stop shop.

Pros:
- Well-documented
- More functional in style
- Well defined, single purposes for each function
- Simple to understand

Cons:
- Many versions, have to search through them
- Verbose (requires much more work to get LOOP-like functionality)

## DOLIST and friends

This set of macros are what I'm using most often right now, mostly because their analogues in Racket are the idiomatic looping constructs in Racket and I feel most comfortable with them. They seem to be a midpoint between LOOP and MAPCAR.

Pros:
- Conciser than MAPCAR, less strange than LOOP
- Simple to understand

Cons:
- Not as well-used or documented
- Hard to program functionally with them (unlike Racket's versions)
- Sometimes misleadingly named

Any insight?

Rob Warnock

unread,
Jul 30, 2017, 3:36:57 AM7/30/17
to
<christop...@gmail.com> wrote:
+---------------
| Hi guys, so I'm new to Common Lisp, having migrated to CL from Racket...
+---------------

Welcome!

[I myself used to use Racket's predecessors MzScheme/MrEd/DrScheme
quite heavily before switching to CL about 15 years ago...]

+---------------
| ...and I have a question about looping constructs. I don't intend to start
| a flamewar, just want some nice pros and cons and general style tips.
+---------------

The best way to avoid flamewars is, fortunately, also the best way to
learn CL style, which is to realize that there is no one "best" way! ;-}
That is, in CL [as in Perl] "there's more than one way to do it":

http://wiki.c2.com/?ThereIsMoreThanOneWayToDoIt

https://en.wikipedia.org/wiki/There%27s_more_than_one_way_to_do_it

That doesn't mean there aren't good ways & bad ways to do things,
but that more emphasis gets put on "good enough" rather than "best".

+---------------
| ## The LOOP macro ...[pros/cons trimmed]...
| ## MAPCAR and friends ...[pros/cons trimmed]...
| ## DOLIST and friends ...[pros/cons trimmed]...
+---------------

Instead of lists of pros & cons, I would suggest picking one or
two variants of each form that seem reasonable to you, and then
use whichever form/variant seems natural in any given situation.

You *DO *NOT* have to learn all of Common Lisp before you
start using it for serious coding!! A subset not much bigger
than Scheme is quite useful.

Then, after you have more experience coding in CL, when you run
across something that doesn't fit the subset of looping/mapping
forms you've chosen, consider extending your toolchest a bit.

[Personally, I tend to always use LOOP instead of DO for pure
iteration, only because I find the syntax for the latter even
more baroque than LOOP. ;-} ]

Anyway, if you're trying to code something specifif and it
doesn't feel quite right, post it here and somebody will
[probably] offer some suggestions.


-Rob

p.s. If you have not already done so, download/unpack a copy
of the Common Lisp HyperSpec (CLHS):

ftp://ftp.lispworks.com/pub/software_tools/reference/HyperSpec-7-0.tar.gz

onto each machine/device on which you will be editing CL code.
It's an invaluable resource [particularly the Symbol Index
and the Permuted Symbol Index].

-----
Rob Warnock <rp...@rpw3.org>
627 26th Avenue <http://rpw3.org/>
San Mateo, CA 94403

Malice

unread,
Jul 30, 2017, 5:51:49 AM7/30/17
to
On Sunday, 30 July 2017 02:02:17 UTC+2, christop...@gmail.com wrote:
> Hi guys, so I'm new to Common Lisp, having migrated to CL from Racket, and I have a question about looping constructs.

Nice to meet you!

>
> ## The LOOP macro
>
> LOOP is super complex, but also insanely powerful and (from a passing comment read somewhere else) quite efficient. There is no analogue in other languages for the mighty LOOP.

The thing is, LOOP isn't anything strange. It's just strange for Lisp. Take a look at Python - you have LOOP in there, although as part of language - we use macros to extend it instead.

In for loop, the basic things to remember are:

(loop repeat N do ...) - when you need to iterate N times and don't bother with keeping index of current element.
(loop for x in list do ...) - when you want to iterate over a list
(loop for x across vec do ...) - when you want to iterate over a vector
(loop ... with something = something-else ...) - when you want to declare the local variables.
(loop ... collecting ...) - when you want to create a list.

This is enough for most cases. Now one more useful thing to remember is that you can use clauses more than once. This is especially useful in for loop:

(loop for x across some-vector
for i from 0 do ...)

Now you are able to track both the element of the vector(it's x), as well as its index (it's i). It's equivalent to something like "for x,i in enumerate(some-list)" in Python.

I often use LOOP for the purpose above, or when the code gets a little bit more complex. Also, LOOP creates an unnamed block, so you can return from it.

There's also ITERATE, which is kind of like Loop, but extensible and a bit different.

> ## MAPCAR and friends
> They are sometimes wasteful because they lack a way to return early, and there are many variants for various things, instead of a one-stop shop.

when you MAP, then by definition, you apply the function to *every* element, so it's not wasteful. mapcar is most useful when you have some function that you want to apply to each element from list. MAP is useful in the same way, but works on any sequence. If you want to add 1 to each element of list, (mapcar #'1+ list) is the best code imho - concise, precise, readable.


> ## DOLIST and friends
>
> This set of macros are what I'm using most often right now, mostly because their analogues in Racket are the idiomatic looping constructs in Racket and I feel most comfortable with them. They seem to be a midpoint between LOOP and MAPCAR.

This is the one I use the least. It's just that most of the time I can either use mapcar or loop, and they are usually more readable/concise. It's a bit easier to control what LOOP returns than DOLIST imho.


Anyway, tl;dr is that they are all used, although I probably use DOLIST the least in my code(actually hardly ever).

- Loop is more imperative - more likely to be used in imperative program.
- Mapcar is more functional - more likely to be used in functional program.
- Mapcar is superior when you want to apply function to the list. If you want to combine functions, use Alexandria library.
- Mapcar and other MAPs, if used as they should be(order does not matter), can be easily parallelised by using lparallel. You just add one letter before each mapping construct and it now works on threads.
- dolist/dotimes etc. are often replaced with LOOP.

Use whatever you like best, but these are my thought on the topic. Ultimately, if the code works, then it does not matter which of these has been used.

Robert Munyer

unread,
Jul 30, 2017, 6:26:55 AM7/30/17
to
You've omitted DO, which is efficient, versatile, well documented, easy
to understand and remember, and can easily be used without side effects.

And recursion (extremely versatile, but not always efficient)
and GO (best avoided, except in rare situations).

And you can create your own looping constructs. See Waters's SERIES,
and Siskind & McAllester's SCREAMER, for awe-inspiring examples.

Two corrections:

"MAP and friends" do have a way to return early, though it's rarely used.

"DOLIST and friends" are not poorly documented, and are used often.

--
-- Robert Munyer code below generates e-mail address

(format nil "~(~{~a~^ ~}~)" (reverse `(com dot munyer at ,(* 175811 53922))))

Pascal J. Bourguignon

unread,
Jul 30, 2017, 8:27:51 AM7/30/17
to
Robert Munyer <rob...@not-for-mail.invalid> writes:

> "MAP and friends" do have a way to return early, though it's rarely used.


Indeed, you can always return early in CL, with a non-local exit, since
the language is highly orthogonal!

(defun double-odds (list)
(mapcar (lambda (x)
(if (evenp x)
(return-from double-odds nil)
(* 2 x)))
list))

(double-odds '(1 3 5 7)) ; --> (2 6 10 14)
(double-odds '(1 3 4 7)) ; --> nil


--
__Pascal J. Bourguignon
http://www.informatimago.com

christop...@gmail.com

unread,
Jul 30, 2017, 10:28:42 AM7/30/17
to
Thank you for the kind answer! I have already decided to use DO & friends for most cases and LOOP where I need extra power. I generally program very functionally but since I don't have anything to prove and this is not a class, I don't generally like using MAPs. I've already got a version of the HyperSpec and set it up for automatic lookup in Emacs, and have already gotten a Scheme-sized subset of CL memorized. I'm working my way through:
* LISP, 3rd Edition
* Practical Common Lisp
* On Lisp
* Let Over Lambda
The above should be enough to work with.

christop...@gmail.com

unread,
Jul 30, 2017, 10:31:23 AM7/30/17
to
Interesting point of view on MAP. Also, its interesting that DO is used least. I generally write very functional code, but LOOP doesn't seem very imperative to me. In fact, it reminds me very strongly of Haskell's list comprehensions.

christop...@gmail.com

unread,
Jul 30, 2017, 10:32:31 AM7/30/17
to
Ah okay. When I say "poorly documented" all I meant was that they don't have many examples in the HyperSpec and are rarely used in the code that I can find. Also, VERY good to know that MAP can return early.

Pascal J. Bourguignon

unread,
Jul 30, 2017, 10:45:21 AM7/30/17
to
christop...@gmail.com writes:
> Thank you for the kind answer! I have already decided to use DO &
> friends for most cases and LOOP where I need extra power.


Notice that one big downside of DO and DO*, is that you often will have
repeated expressions for the inialization and increment parts of several
variables.


(do* ((i 0 (1+ i)) ; Ok.
(p list (rest p)) ; So far so good.
(f (first p) (first p)) ; What the f…
(e (aref a i) (aref a i)) ; Really?
(v (read input) (read input))) ; When will it end???
((null p))
(do-something f e v))

compare with:

(loop
:for i :from 0
:for f :in list
:for e := (aref a i)
:for v := (read input)
:do (something f e v))


(loop :with v [:= e] …) let's you eat surrounding LET/LET* forms too.


> I generally
> program very functionally but since I don't have anything to prove and
> this is not a class, I don't generally like using MAPs. I've already
> got a version of the HyperSpec and set it up for automatic lookup in
> Emacs, and have already gotten a Scheme-sized subset of CL
> memorized. I'm working my way through:
> * LISP, 3rd Edition
> * Practical Common Lisp
> * On Lisp
> * Let Over Lambda
> The above should be enough to work with.

christop...@gmail.com

unread,
Jul 30, 2017, 11:34:43 AM7/30/17
to
On Sunday, July 30, 2017 at 7:45:21 AM UTC-7, informatimago wrote:
> christop...@gmail.com writes:
> > Thank you for the kind answer! I have already decided to use DO &
> > friends for most cases and LOOP where I need extra power.
>
>
> Notice that one big downside of DO and DO*, is that you often will have
> repeated expressions for the inialization and increment parts of several
> variables.
>
>
> (do* ((i 0 (1+ i)) ; Ok.
> (p list (rest p)) ; So far so good.
> (f (first p) (first p)) ; What the f…
> (e (aref a i) (aref a i)) ; Really?
> (v (read input) (read input))) ; When will it end???
> ((null p))
> (do-something f e v))
>

Hmm, good point. I really hate the looks of that. I guess it really is just a case of using what's best for the job, although DO does tend to lend itself to repeated stuff (which is my bane, I hate it, that's why I use Lisp), do DO's children (DOLIST, etc) do this too? (Do be do be do).

> compare with:
>
> (loop
> :for i :from 0
> :for f :in list
> :for e := (aref a i)
> :for v := (read input)
> :do (something f e v))
>
>

Interesting that you can use keywords instead of plain words. I think I like that better: it makes it clearer where LOOP ends and Lisp begins.

> (loop :with v [:= e] …) let's you eat surrounding LET/LET* forms too.
>

Yay, less LET!

Kaz Kylheku

unread,
Jul 30, 2017, 12:18:31 PM7/30/17
to
On 2017-07-30, Pascal J. Bourguignon <p...@informatimago.com> wrote:
> christop...@gmail.com writes:
>> Thank you for the kind answer! I have already decided to use DO &
>> friends for most cases and LOOP where I need extra power.
>
>
> Notice that one big downside of DO and DO*, is that you often will have
> repeated expressions for the inialization and increment parts of several
> variables.
>
>
> (do* ((i 0 (1+ i)) ; Ok.
> (p list (rest p)) ; So far so good.
> (f (first p) (first p)) ; What the f…
> (e (aref a i) (aref a i)) ; Really?
> (v (read input) (read input))) ; When will it end???
> ((null p))
> (do-something f e v))

DO should be designed such that if a variable has a single expression,
that same expression is used to update it; so that (e (aref a i)) means
(e (aref a i) (aref a i)). In other words LOOP FOR semantics rather
than LOOP WITH.

Loop variables which don't fit the paradigm can be defined in a
surrounding let. That itself adds clarity; non-incrementing or
alternatively-incrementing vars are not distractingly mixed up among the
DO vars.

Also, loop variables which don't increment can be specified just by
repeating their name as the second initform:

(x (whatever y) x) ;; next value of x is x

This repetition is arguably less irksome than the repetition of a
compound form.

Robert Munyer

unread,
Jul 30, 2017, 6:47:03 PM7/30/17
to
>> Notice that one big downside of DO and DO*, is that you often will have
>> repeated expressions for the inialization and increment parts of several
>> variables.
>>
>>
>> (do* ((i 0 (1+ i)) ; Ok.
>> (p list (rest p)) ; So far so good.
>> (f (first p) (first p)) ; What the f…
>> (e (aref a i) (aref a i)) ; Really?
>> (v (read input) (read input))) ; When will it end???
>> ((null p))
>> (do-something f e v))

I agree that that's a disadvantage.
But I very often find that simple refactoring makes it go away...

(do ((i 0 (1+ i))
(p list (rest p)))
((null p))
(let ((f (first p))
(e (aref a i))
(v (read input)))
(do-something f e v)))

> do DO's children (DOLIST, etc) do this too?

No. DOLIST, like MAP, hides the loop variable manipulation.

Pascal J. Bourguignon

unread,
Jul 30, 2017, 7:45:54 PM7/30/17
to
Robert Munyer <rob...@not-for-mail.invalid> writes:

>>> Notice that one big downside of DO and DO*, is that you often will have
>>> repeated expressions for the inialization and increment parts of several
>>> variables.
>>>
>>>
>>> (do* ((i 0 (1+ i)) ; Ok.
>>> (p list (rest p)) ; So far so good.
>>> (f (first p) (first p)) ; What the f…
>>> (e (aref a i) (aref a i)) ; Really?
>>> (v (read input) (read input))) ; When will it end???
>>> ((null p))
>>> (do-something f e v))
>
> I agree that that's a disadvantage.
> But I very often find that simple refactoring makes it go away...
>
> (do ((i 0 (1+ i))
> (p list (rest p)))
> ((null p))
> (let ((f (first p))
> (e (aref a i))
> (v (read input)))
> (do-something f e v)))

Good point.


>> do DO's children (DOLIST, etc) do this too?
>
> No. DOLIST, like MAP, hides the loop variable manipulation.

--

Spiros Bousbouras

unread,
Jul 30, 2017, 7:45:57 PM7/30/17
to
On Sun, 30 Jul 2017 22:46:57 +0000 (UTC)
Robert Munyer <rob...@not-for-mail.invalid> wrote:
> >> Notice that one big downside of DO and DO*, is that you often will have
> >> repeated expressions for the inialization and increment parts of several
> >> variables.
> >>
> >>
> >> (do* ((i 0 (1+ i)) ; Ok.
> >> (p list (rest p)) ; So far so good.
> >> (f (first p) (first p)) ; What the f...
> >> (e (aref a i) (aref a i)) ; Really?
> >> (v (read input) (read input))) ; When will it end???
> >> ((null p))
> >> (do-something f e v))
>
> I agree that that's a disadvantage.
> But I very often find that simple refactoring makes it go away...
>
> (do ((i 0 (1+ i))
> (p list (rest p)))
> ((null p))
> (let ((f (first p))
> (e (aref a i))
> (v (read input)))
> (do-something f e v)))

Or more simply

(do ((i 0 (1+ i))
(p list (rest p))
f e v)
((null p))
(setq f (first p) e (aref a i) v (read input))
(do-something f e v))

But if one also wants type declarations for f , e , v and the types
are not compatible with NIL then one would need an additional LET or
LOCALLY or repeat the expressions in the clauses of DO .

Pascal J. Bourguignon

unread,
Jul 30, 2017, 8:46:33 PM7/30/17
to
This is actually much more complex!

Assignment is way more complex than binding. Both for the compiler,
who has to do a lot of work to decide if it can eliminate the variable
or not, and for the human programmer in terms of cognitive load, and for
the human readers (maintainers, debuggers, etc) who have to do a lot of
work to determine if the variables are assigned only once or several
times, and what value they may _now_ have, etc.


It's crazy, I don't understand how you could imagine it is "more
simply"…


> But if one also wants type declarations for f , e , v and the types
> are not compatible with NIL then one would need an additional LET or
> LOCALLY or repeat the expressions in the clauses of DO .

Spiros Bousbouras

unread,
Jul 30, 2017, 9:18:44 PM7/30/17
to
On Mon, 31 Jul 2017 02:46:24 +0200
"Pascal J. Bourguignon" <p...@informatimago.com> wrote:
I wasn't thinking how much work a compiler would need to do but in any case I
don't see how with the first code snippet the compiler would need to do less
work than with the second. But with the first a naive implementation would do
allocation for f , e , v with every repetition which would slow things
down. In order not to do allocation it would need to at least prove that the
(let ...) part does not return a closure.

> and for the human programmer in terms of cognitive load,

My congnitive load with the code I posted is less than with the code I
quoted at least for the reason that I avoid an extra indentation level.

> and for
> the human readers (maintainers, debuggers, etc) who have to do a lot of
> work to determine if the variables are assigned only once or several
> times, and what value they may _now_ have, etc.

You are being silly. It's trivial to see that f , e , v get assigned
with every repetition of the loop and they have the value since the
last SETQ .And why would one care whether they get assigned once or
several times ?

> It's crazy, I don't understand how you could imagine it is "more
> simply"...

I don't need to imagine it , I see it in front of my eyes.

If you really think that assignment is so horrible , shouldn't you be
programming in some pure functional language ?

--
Any sufficiently advanced bug is indistinguishable from a feature.
Rich Kulawiec

Rob Warnock

unread,
Jul 31, 2017, 2:30:31 AM7/31/17
to
Spiros Bousbouras <spi...@gmail.com> wrote:
+---------------
| "Pascal J. Bourguignon" <p...@informatimago.com> wrote:
...[argument about style of DO with auxialiary variables]...
+---------------

This is probably a good place to point out to the OP that,
despite their apparent similarities, the Scheme & CL versions
of DO have a subtle difference in the treatment of updating
loop variables, namely, in Scheme *every* update of a loop
variable is done by binding the variable to a fresh location,
whereas in CL only the initial setting is by binding and
subsequent settings [from a "step form"] are by *assignment*:

http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.2.4
4.2.4 Iteration
library syntax:
(do ((<variable1> <init1> <step1>) ...)
(<test> <expression> ...)
<command> ...)
...
Each iteration begins by evaluating <test>; if the result
is false (see section 6.3.1), then the <command> expressions
are evaluated in order for effect, the <step> expressions
are evaluated in some unspecified order, the <variable>s
are bound to fresh locations, the results of the <step>s
are stored in the bindings of the <variable>s, and the
next iteration begins.
...

Compare with:

http://www.lispworks.com/documentation/HyperSpec/Body/m_do_do.htm
Macro DO, DO*
Syntax:
do ({var | (var [init-form [step-form]])}*)
(end-test-form result-form*)
declaration*
{tag | statement}*
=> result*
...
Before the first iteration, all the init-forms are evaluated,
and each var is bound to the value of its respective init-form,
if supplied. This is a binding, not an assignment; when the loop
terminates, the old values of those variables will be restored.
...
At the beginning of each iteration other than the first, vars
are updated as follows. All the step-forms, if supplied, are
evaluated, from left to right, and the resulting values are
assigned to the respective vars. Any var that has no
associated step-form is not assigned to.
...

This is a subtle difference, to be sure, but it can result in
observable effects when loop variables are captured [closed over
by lambdas] in the body of the loop.

Compare this bit of Scheme:

(do ((i 0 (1+ i))
(fns '()))
((> i 4) (map (lambda (x) (x)) (reverse fns)))
(set! fns (cons (lambda () i) fns)))
==>
(0 1 2 3 4)

with the CL equivalent:

(do ((i 0 (1+ i))
(fns '()))
((> i 4) (mapcar #'funcall (reverse fns)))
(setq fns (cons (lambda () i) fns)))
==>
(5 5 5 5 5)

Both answers are "correct" for the corresponding language.


-Rob

Robert L.

unread,
Jul 31, 2017, 11:06:02 AM7/31/17
to
On 7/30/2017, Pascal J. Bourguignon wrote:

> compare with:
>
> (loop
> :for i :from 0
> :for f :in list
> :for e := (aref a i)
> :for v := (read input)
> :do (something f e v))


Compare with:

(use srfi-42) ; do-ec

(do-ec (:parallel (:list f mylist)
(:vector e myvector)
(:port v input))
(something f e v))

--
Amazon bans book after it received 300 5-star reviews.
http://www.tomatobubble.com/worldwarii.html

Robert L.

unread,
Jul 31, 2017, 12:03:45 PM7/31/17
to
On 7/30/2017, Spiros Bousbouras wrote:

> > Assignment is way more complex than binding. Both for the compiler,
> > who has to do a lot of work to decide if it can eliminate the variable
> > or not,
>
> I wasn't thinking how much work a compiler would need to do but in any case I
> don't see how with the first code snippet the compiler would need to do less
> work than with the second. But with the first a naive implementation would do
> allocation for f , e , v with every repetition which would slow things
> down. In order not to do allocation it would need to at least prove that the
> (let ...) part does not return a closure.

One implementor of Scheme, giving advice for making faster programs,
wrote:

... assignments make flow-analysis harder and should be
avoided.

A small program to test that assertion.


(define-constant iters 9666555)

(time
(let loop ((i iters) (sum 0.0))
(if (fx= 0 i) sum
(loop (fx- i 1) (fp+ sum 2.5)))))

(time
(let ((i iters) (sum 0.0))
(let loop ()
(if (fx= 0 i) sum
(begin (set! sum (fp+ sum 2.5))
(set! i (fx- i 1))
(loop))))))


After compiling with -O5:

0.062s CPU time, 2/1178 GCs (major/minor), maximum live heap: 119.25 KiB
0.406s CPU time, 9666555/9658378 mutations (total/tracked), 2/1178 GCs
(major/minor), maximum live heap: 119.25 KiB

The binding version is 6.5 times as fast as the assignment version.

Robert Munyer

unread,
Jul 31, 2017, 6:41:55 PM7/31/17
to
Rob Warnock wrote:

> (do ((i 0 (1+ i))
> (fns '()))
> ((> i 4) (mapcar #'funcall (reverse fns)))
> (setq fns (cons (lambda () i) fns)))
> ==>
> (5 5 5 5 5)

You can get the Scheme behavior in Common Lisp, by wrapping a
LET form around the LAMBDA form or the CONS form or the SETQ form.

You can give the LET variable the same name as the DO variable.
Some people consider this bad style, but I actually like it because
it makes it very clear to the reader that the DO variable will not
be used inside the LET body.

off-topic:

I learned the Go language for a project last year. Go's "for"
behaves like CL's DO, but beginners often use it as if it behaved
like Scheme's DO, leading to the "5 5 5 5 5" result that Rob W.
demonstrated above, a bug so common that it's featured prominently
in most of the "common mistakes" articles about Go programming.

Shadowing a DO variable with a LET variable is a common idiom in
Go, even recommended in the official FAQ. (Search for the string
"v := v" in https://golang.org/doc/faq .)

Pascal J. Bourguignon

unread,
Jul 31, 2017, 6:55:24 PM7/31/17
to
Robert Munyer <rob...@not-for-mail.invalid> writes:

> Rob Warnock wrote:
>
>> (do ((i 0 (1+ i))
>> (fns '()))
>> ((> i 4) (mapcar #'funcall (reverse fns)))
>> (setq fns (cons (lambda () i) fns)))
>> ==>
>> (5 5 5 5 5)
>
> You can get the Scheme behavior in Common Lisp, by wrapping a
> LET form around the LAMBDA form or the CONS form or the SETQ form.
>
> You can give the LET variable the same name as the DO variable.
> Some people consider this bad style, but I actually like it because
> it makes it very clear to the reader that the DO variable will not
> be used inside the LET body.
>
> off-topic:
>
> I learned the Go language for a project last year. Go's "for"
> behaves like CL's DO, but beginners often use it as if it behaved
> like Scheme's DO, leading to the "5 5 5 5 5" result that Rob W.
> demonstrated above, a bug so common that it's featured prominently
> in most of the "common mistakes" articles about Go programming.
>
> Shadowing a DO variable with a LET variable is a common idiom in
> Go, even recommended in the official FAQ. (Search for the string
> "v := v" in https://golang.org/doc/faq .)

Only too bad binding a new variable has the same syntax as assignment…
But it cannot be worse than Python.

Pascal Costanza

unread,
Aug 1, 2017, 6:51:48 AM8/1/17
to
No, := is binding. Assignment is = in Go.

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
The views expressed are my own, and not those of my employer.

Pascal J. Bourguignon

unread,
Aug 1, 2017, 8:53:40 AM8/1/17
to
Even worse, using confusing line-noise.

Kaz Kylheku

unread,
Aug 1, 2017, 11:22:29 AM8/1/17
to
On 2017-08-01, Pascal J. Bourguignon <p...@informatimago.com> wrote:
> Pascal Costanza <p...@p-cos.net> writes:
>>> Only too bad binding a new variable has the same syntax as assignment…
>>> But it cannot be worse than Python.
>>>
>>
>> No, := is binding. Assignment is = in Go.
>
> Even worse, using confusing line-noise.

Yuck, indeed! Reminds me of the recently seen (in this very thread):

Pascal J. Bourguignon

unread,
Aug 1, 2017, 12:04:58 PM8/1/17
to
:-)

Robert L.

unread,
Aug 1, 2017, 7:48:41 PM8/1/17
to
On 7/29/2017, christop...@gmail.com wrote:

> Cons:
> - Many versions, have to search through them

Nonsense.

The size of the documentation for the mapping functions
is much less than the size of the documentation for loop.


> - Verbose (requires much more work to get LOOP-like functionality)

Nonsense.

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

(map f list)


(loop for e in list when (foo-bar e x) collect it))

(filter-map (cut foo-bar <> x) list)


(loop for x in them
when (yada x)
collect x)

(filter yada them)

Barry Margolin

unread,
Aug 2, 2017, 11:01:15 AM8/2/17
to
In article <olobm1$mj8$1...@gioia.aioe.org>,
Robert Munyer <rob...@not-for-mail.invalid> wrote:

> Rob Warnock wrote:
>
> > (do ((i 0 (1+ i))
> > (fns '()))
> > ((> i 4) (mapcar #'funcall (reverse fns)))
> > (setq fns (cons (lambda () i) fns)))
> > ==>
> > (5 5 5 5 5)
>
> You can get the Scheme behavior in Common Lisp, by wrapping a
> LET form around the LAMBDA form or the CONS form or the SETQ form.
>
> You can give the LET variable the same name as the DO variable.
> Some people consider this bad style, but I actually like it because
> it makes it very clear to the reader that the DO variable will not
> be used inside the LET body.
>
> off-topic:
>
> I learned the Go language for a project last year. Go's "for"
> behaves like CL's DO, but beginners often use it as if it behaved
> like Scheme's DO, leading to the "5 5 5 5 5" result that Rob W.
> demonstrated above, a bug so common that it's featured prominently
> in most of the "common mistakes" articles about Go programming.

It's also a very common error in Javascript, and the Stack Overflow
questions that explain how to resolve it are some of the most frequently
used as duplicate references.

https://stackoverflow.com/questions/1451009/javascript-infamous-loop-issu
e
https://stackoverflow.com/questions/750486/javascript-closure-inside-loop
s-simple-practical-example

--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
0 new messages