I'm new to LISP and I've got a real bugger of
a problem here : I need to define a function
which duplicates every occurance of a given
element in a given list. Suppose this funtion
was called DUPLIC8, then the call
(DUPLIC8 '(A B C D E B) 'B))
should produce (A B B C D E B B).
Cheers.
P.S. Must be easy as winking, this one for you.
I could write this one in C with my eyes closed but
LISP is just not up my valley. No offence, though.
--
Lang may yer lum reek!
~~~~~
/ \ | | | |
/ \~~~~~~~~~~~~\
> Interesting homework assignment. Sounds like a job for "mapcar".
Nope! MAPCAR won't do it! :)
> Private Oracle <no...@atlant.ru> wrote in message
> news:01be763a$7e1f16e0$Loca...@ppnorn.atlant.ru...
> >
> > Hi, folks! Your help badly needed!
> >
> > I'm new to LISP and I've got a real bugger of
> > a problem here : I need to define a function
> > which duplicates every occurance of a given
> > element in a given list. Suppose this funtion
> > was called DUPLIC8, then the call
> >
> > (DUPLIC8 '(A B C D E B) 'B))
> >
> > should produce (A B B C D E B B).
> >
> > Cheers.
> >
> > P.S. Must be easy as winking, this one for you.
> > I could write this one in C with my eyes closed but
> > LISP is just not up my valley. No offence, though.
> >
Let's help the student!
(defun duplic8 (the-list element)
(cond ((null the-list) '())
((eql element (first the-list))
(append (list element element)
(duplic8 (rest the-list) element)))
(t (cons (first the-list) (duplic8 (rest the-list) element)))))
(defun duplic8 (the-list element)
(mapcan #'(lambda (x) ;; You need MAPCAN
(if (eql x element)
(list x x)
(list x)))
the-list))
(defun duplic8 (the-list element)
(loop for x in the-list
if (eql x element)
nconc (list x x)
else
nconc (list x)))
(defun duplic8 (the-list element)
(loop for x in the-list
if (eql x element)
append (list x x)
else
append (list x)))
--
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
http://www.parades.rm.cnr.it/~marcoxa
> "Fred Martin" <marti...@home.com> writes:
>
> > Interesting homework assignment. Sounds like a job for "mapcar".
>
> Nope! MAPCAR won't do it! :)
Hmmm?
(defun duplic8 (list item)
(apply #'append
(mapcar (lambda (x)
(if (eql x item)
(list x x)
(list x)))
list)))
--- which does not mean that I think it's a particularly
elegant solution :-)
--
Raymond Wiker, Orion Systems AS
+47 370 61150
One of the utilities I always have lying around is mapappend, which works
like mapcar, but has the effect of appending together all the result lists.
(there's a definition e.g. in AoMOP). With such a utiltiy defined you could
write:
(defun duplic8 (list item)
(mapappend #'(lambda (x) (cons x (when (eql x item) (list x)))) list))
(Yuck! :-))
-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own
I don't think "mapcar" can do it in one pass. I think you need
another function from the same family to do this functionally.
I believe there is also a simple recursive solution, and you can
do anything with LOOP if you understand it well enough.
>Private Oracle <no...@atlant.ru> wrote in message
>news:01be763a$7e1f16e0$Loca...@ppnorn.atlant.ru...
>>
>> Hi, folks! Your help badly needed!
>>
>> I'm new to LISP and I've got a real bugger of
>> a problem here : I need to define a function
>> which duplicates every occurance of a given
>> element in a given list. Suppose this funtion
>> was called DUPLIC8, then the call
>>
>> (DUPLIC8 '(A B C D E B) 'B))
>>
>> should produce (A B B C D E B B).
>>
>> Cheers.
>>
>> P.S. Must be easy as winking, this one for you.
>> I could write this one in C with my eyes closed but
>> LISP is just not up my valley. No offence, though.
>>
>>
>> --
>> Lang may yer lum reek!
>> ~~~~~
>> / \ | | | |
>> / \~~~~~~~~~~~~\
>
>
--
Christopher R. Eliot, Senior Postdoctoral Research Associate
Center for Knowledge Communication, Department of Computer Science
University of Massachusetts, Amherst 01003. (413) 545-4248 FAX: 545-1249
EL...@cs.umass.edu, <http://www.cs.umass.edu/~eliot/>
> One of the utilities I always have lying around is mapappend, which works
> like mapcar, but has the effect of appending together all the result lists.
> (there's a definition e.g. in AoMOP). With such a utiltiy defined you could
> write:
>
> (defun duplic8 (list item)
> (mapappend #'(lambda (x) (cons x (when (eql x item) (list x)))) list))
What's wrong with MAPCAN?
--
Gareth McCaughan Dept. of Pure Mathematics & Mathematical Statistics,
gj...@dpmms.cam.ac.uk Cambridge University, England.
"Private Oracle" <no...@atlant.ru> writes:
> I was wondering what MAPCAR does. Could anyone help
MAPCAR is a mapping function that takes a function and a list as arguments,
and produces a result list composed of the result of calling the function
on the items of the list.
Examples:
(mapcar #'1+ '(1 2 3)) => (2 3 4)
(mapcar #'+ '(1 2 3) '(4 5 6 7)) => (5 7 9)
A simple (incomplete) implementation is:
(defun imapcar (fn list)
(if (null list)
nil
(cons (funcall fn (car list)) (imapcar fn (cdr list)))))
A more complete implementation that handles multiple lists (example 2) is:
(defun mapcar (fn first-list &rest rest-lists)
(if (or (null first-list) (some #'null rest-lists))
nil
(cons (apply fn (car first-list) (mapcar #'car rest-lists))
(apply #'mapcar fn (cdr first-list) (mapcar #'cdr rest-lists)))))
Not very pretty, but the second version simply generalizes the first to
work with multiple lists as input. Note that the function must be defined
appropriately to take the right number of arguments, or an error will
result.
Pick up your favorite lisp textbook to find out more.
Sunil
(defun duplic8 (cons list) (mapcan #'(lambda (eq)(cons eq (if (eq eq
list)(list eq))))cons))
For extra credit, explain how this function works and why it is safe
to use MAPCAN here.
Stig Hemmer,
Jack of a Few Trades.
> (defun duplic8 (the-list element)
> (loop for x in the-list
> if (eql x element)
> append (list x x)
> else
> append (list x)))
If you like loop, I like this better:
(defun duplic8 (the-list element)
(loop for x in the-list
collect x
when (eql x element) collect x))
(defun duplic8 (the-list element)
(loop for x in the-list
if (eql x element)
collect x and collect x
else
collect x))
(defun duplic8 (the-list element)
(loop for x in the-list
collect x ; Always
when (eql x element)
collect x))
--
Thomas A. Russ, USC/Information Sciences Institute t...@isi.edu
> Marco Antoniotti wrote:
>
> > (defun duplic8 (the-list element)
> > (loop for x in the-list
> > if (eql x element)
> > append (list x x)
> > else
> > append (list x)))
>
> If you like loop, I like this better:
>
> (defun duplic8 (the-list element)
> (loop for x in the-list
> collect x
> when (eql x element) collect x))
Beautiful!
>> Marco Antoniotti wrote:
>>
>> > (defun duplic8 (the-list element)
>> > (loop for x in the-list
>> > if (eql x element)
>> > append (list x x)
>> > else
>> > append (list x)))
>>
>> If you like loop, I like this better:
>>
>> (defun duplic8 (the-list element)
>> (loop for x in the-list
>> collect x
>> when (eql x element) collect x))
>
There's a question at the end of this longish response--please keep
reading...
I've begun writing functions like the above more and more like this:
(defun duplic9 (list predicate)
(cond
((null list) '())
((funcall predicate (car list))
(cons (car list) (duplic8 (cdr list) predicate)))
(t (duplic9 (cdr list) predicate)))))
or the loop version:
(defun duplic9 (list predicate)
(loop for x in list
collect x
when (funcall predicate x) collect x))
based on a comment--I think by Olin Shivers in discussions about a standard
list library for Scheme--that when you see a predicate like EQL embedded in
a function, you're not capturing the right abstraction--it's like having any
other constant embedded in your function.
Of course, to capture the exact semantics of the original duplic8 function,
you would call, for example:
(duplic9 '(a b c d e) #'(lambda (item) (eql item 'd)))
or write duplic8 in terms of duplic9:
(defun duplic8 (list elt)
(duplic9 list #'(lambda (item) (eql elt item))))
Here's the question/comment: Using all of these anonymous lambdas are fun
and all, but they cause a real headache while debugging. The environments I
tend to use, at least, give very cryptic comments when errors occur with
these, things like:
ERROR: NIL can't be funcalled or applied, in an anonymous lambda form inside
an anonymous lambda form, inside an anonymous lambda form.
Backtracing shows lots of anonymous lambda forms...
Seriously, what tricks do people have for debugging in the face of lots of
anonymous lambdas?
Will Fitzgerald
...
> Seriously, what tricks do people have for debugging in the face of lots of
> anonymous lambdas?
>
> Will Fitzgerald
>
One trick is to use lists instead of lambdas:
i.e instead of
(funcall callback arg)
(apply (car callback) arg (cdr callback))
Then instead of
#'(lambda (x) (foo x y)
you use
`(foo ,y)
This sometimes makes it easier to do debugging.
(Excuse the untested lisp)
__Jason
> Backtracing shows lots of anonymous lambda forms...
>
> Seriously, what tricks do people have for debugging in the face of lots of
> anonymous lambdas?
Well, most Lisps name their anonymous lambdas better.
My recollection is that Genera compiled duplic8 above as if you'd done
(duplic9 list #'(:internal duplic8 0))
Genera had a generalized notion of function names based on compounds,
so that #'(:internal (:internal foo 0) 3) would mean the fourth
definition in the first definition in foo, for example. This encapsulation
facility is also used to support tracing, etc. Other implementations
might flatten the names to symbols, but you should send bug reports
if they can't give you any debugging info at all.
Even with that, you sometimes also want:
(defun make-tester (item)
#'(lambda (test) (eql item test)))
So that you call (foo (make-tester 3)) instead of
directly calling the closure so that you see
(:internal make-tester 0)
instead of
(:internal function-needing-the-test 0)
This also potentially increases sharing. But if lambdas give you
junky names in the debugger, you're pretty much stuck.
Bug reports and your willingness to change vendors are critical in
cases like this.
Scheme has to do it that way because it has so weak lambda lists, with
neither optional nor keyword arguments, and no macro system to build
them, either.
I prefer to use KEY and TEST keyword arguments that default to #'IDENTITY
and #'EQL.
(defun duplic8 (element list &key (test #'eql) (key #'identity))
(loop for x in list
collect x
when (funcall test (funcall key x) element) collect x))
incidentally, "any other constant embedded in your function" does not
sound like a real argument, since functions by their very nature must
have a number of constants.
#:Erik
[snip]
> Here's the question/comment: Using all of these anonymous lambdas are fun
> and all, but they cause a real headache while debugging. The environments I
> tend to use, at least, give very cryptic comments when errors occur with
> these, things like:
>
> ERROR: NIL can't be funcalled or applied, in an anonymous lambda form inside
> an anonymous lambda form, inside an anonymous lambda form.
>
> Backtracing shows lots of anonymous lambda forms...
>
> Seriously, what tricks do people have for debugging in the face of lots of
> anonymous lambdas?
Alan Ruttenberg wrote a patch for MCL that names anonymous lambdas in a way
similar to what Kent has described in this thread.
(defun foo ()
(mapc #'(lambda (x) (break) x)
(list 1 2 3)))
(foo)
> Break:
> While executing: (:LAMBDA 1 :IN FOO)
> Type Command-/ to continue, Command-. to abort.
> If continued: Return from BREAK.
See the RestartsÅ menu item for further choices.
1 >
Look for anonymous-not!.lisp on
<http://alanr.www.media.mit.edu/people/alanr/dev+.html>.
--
David B. Lamkins <http://www.teleport.com/~dlamkins/>
Wintel is the Yugo of the computer world: cheap to buy, costly to keep.
Not that I put much hope in this, but just for completeness,
does it get better with (OPTIMIZE DEBUG)?
Vassil Nikolov <vnik...@poboxes.com> www.poboxes.com/vnikolov
(You may want to cc your posting to me if I _have_ to see it.)
LEGEMANVALEMFVTVTVM (Ancient Roman programmers' adage.)
Nothing, I guess, but I normally avoid writing anything with destructive
functions first time around.
Well, I didn't say it very well--all I meant was that it makes sense for the
equality test not be a constant embedded in the function, but should be a
parameter to the function, since the nature of the 'duplic8' function was to
collect equivalent things. And, and KMP has taught us all by now, one
equivalence predicate just isn't enough.
Shiver's original statement was:
"I usually take a commitment to a particular equality predicate as a
sign of poor design."
embedded within
<http://srfi.schemers.org/srfi-1/mail-archive/msg00033.html>. So you can see
I was stretching his comments a bit.
> In article <86677pm...@g.pet.cam.ac.uk>,
> Gareth McCaughan <gj...@dpmms.cam.ac.uk> wrote:
> >
> > What's wrong with MAPCAN?
>
> Nothing, I guess, but I normally avoid writing anything with destructive
> functions first time around.
Why?
This piece of religious nonsense needs to have a wooden stake driven
through its heart, IMO. Apologies in advance for coming down hard on it,
but his place would be no fun without someone having a bit of zeal about
their opinions, and if people are going to say destructive is bad, they'd
better be prepared to hear the alternative point of view.
If I reword this to say exactly the same thing in a different tone,
is it really defensible?
"I avoid writing anything using obvious and efficient tools
first time around."
When you prepare dinner, do you start by cutting through all your fruits
and vegetables with imaginary, non-destructive implements hoping that will
be enough and come back to use a real knife only if you find out that
doesn't work? Some engineering practice involves the use of tools which
are dangerous if used wrong but still the right thing to apply on the
very first task.
MAPCAN can have its problems and may not be the right thing to use for
reasons of conceptual understanding. But using MAPAPPEND (if there
were such a thing) would not substantially fix many of the problems with
MAPCAN. And certainly there are many cases where using NREVERSE (another
destructive operator) is absolutely the right thing to do. The following
idiom should never be written first with REVERSE:
(let ((l '()))
(dolist (f frobs)
(if (good-p f) (push f l)))
(nreverse l))
Note also the use of PUSH. You could claim that was destructive and to be
avoided, too, but to what possible end?
Style is one thing, dogma is quite another.
Saying "destructive is bad" seems to me indefensible.
Just my opinion.
Feel free to defend the alternative. ;-)
> His dogma ran over his lambda.
Shouldn't that be "His dogma ran away with his lambda" (makes
marginally more sense to me... :-)
Besides, `if people are going to say destructive is bad, they'd
better' write their own sorting routine and forget about CL:SORT.
(Quoted phrase from article <sfwzp4z...@world.std.com> by
Kent M Pitman <pit...@world.std.com>.)
Vassil Nikolov <vnik...@poboxes.com> www.poboxes.com/vnikolov
(You may want to cc your posting to me if I _have_ to see it.)
LEGEMANVALEMFVTVTVM (Ancient Roman programmers' adage.)
-----------== Posted via Deja News, The Discussion Network ==----------
On Sat, 27 Mar 1999 00:01:37 GMT, Kent M Pitman <pit...@world.std.com> wrote:
>pr...@my-dejanews.com writes:
>
>> In article <86677pm...@g.pet.cam.ac.uk>,
>> Gareth McCaughan <gj...@dpmms.cam.ac.uk> wrote:
>> >
>> > What's wrong with MAPCAN?
>>
>> Nothing, I guess, but I normally avoid writing anything with destructive
>> functions first time around.
>
>Why?
>
>This piece of religious nonsense needs to have a wooden stake driven
>through its heart, IMO. Apologies in advance for coming down hard on it,
>but his place would be no fun without someone having a bit of zeal about
>their opinions, and if people are going to say destructive is bad, they'd
>better be prepared to hear the alternative point of view.
[Thought provoking example deleted to conserve space.]
>Style is one thing, dogma is quite another.
>Saying "destructive is bad" seems to me indefensible.
>Just my opinion.
>Feel free to defend the alternative. ;-)
For me, attempting a purely functional solution first is an attempt to
re-adjust my thinking from the thorough procedural endoctrination I
have recieved. As I gain more maturity in Lisp, I suspect that I too
will develop the skills of *how* (and the instinct to know *when*) to
code a functional solution. Until then, I purposefully follow a
non-destructive dogma to gain experience. (I suspect at that point I
will reach the same conclusion as Kent.)
Mark
--
Mark K. Gardner (mkga...@cs.uiuc.edu)
University of Illinois at Urbana-Champaign
Real-Time Systems Laboratory
--
It's got nothing to do with religion. But it does help in writing bug-free
code. Of course when things need to be done effeciently then it's worth
investing the effort, but there's an extra level of checking required when
bits of code aren't purely functional.
> It's got nothing to do with religion. But it does help in writing bug-free
> code. Of course when things need to be done effeciently then it's worth
> investing the effort, but there's an extra level of checking required when
> bits of code aren't purely functional.
Fortunately the extra level of checking is as nothing compared to the
years of mental gymnastics spent trying to represent the system you're
trying to model in a purely functional way.
--tim
> It's got nothing to do with religion. But it does help in writing bug-free
> code. Of course when things need to be done effeciently then it's worth
> investing the effort, but there's an extra level of checking required when
> bits of code aren't purely functional.
>
In many idiomatic uses of destructive operations, there isn't any
effort to be invested. For instance,
(let ((acc nil))
(dolist (.....)
......
(push item acc))
(nreverse acc))
is an idiomatic way to collect some stuff together. NREVERSE is
destructive but the function this is part of is still purely
functional since it only destructively modifies things it has itself
created.
Likewise structs, arrays, vectors and objects can be reasonably
modified destructively in many cases.
The non-religious view of destructive operations is that they can
cause problems since they constrain the possible arguments of things
to be non-shared and so they should be used where appropriate. Just
like the good old GOTO and most of the others stuff some dogma's tell
you to never do or always do.
--
Lieven Marchand <m...@bewoner.dma.be>
If there are aliens, they play Go. -- Lasker
I don't agree with that (expect for IO). There are perfectly reasonable
purely functional programs, and indeed, languages in which you have to work
hard to do anything else.
To restate. In the normal course of things I use cons rather than push,
append rather than nconc etc. because this tends to produce more
maintainable, and bug- free code.
To be honest I'm a little confused that others are disagreeing with this.
Look through code that is posted here, or in books, or on ftp sites; and this
is the way most CL code is written. There are plenty of places where
destructive functions could replace their non-destructive counterparts, but
people don't bother except where it really matters. The overhead in checking
whether side effects are desirable (or at least no harmful) usual isn't worth
it in order to save a little bit of garbage.
> I don't agree with that (expect for IO). There are perfectly reasonable
> purely functional programs, and indeed, languages in which you have to work
> hard to do anything else.
>
But is there a perfecly reasonable purely functional *world* out
there? That tends to be a more interesting question for many people
trying to write programs which talk about stuff outside the machine.
--tim
that's a clear sign there's enlightenment to be found in becoming
unconfused...
what people object to is the creation of garbage for no purpose, not the
value of a more functional style. just as with any other resource, there
is no excuse for wanton waste, but that's what you favor and you think
the textbooks favor, but the latter is not quite so.
textbooks are allowed to introduce complexity in steps. students are
supposed to learn the full complexity of the issue through these steps,
not stay at some step and not move on. you argue as if students should
_not_ move on, because the complexity of the "don't wantonly waste the
resources" supposedly produces less maintainable and buggier code. if I
were to assume a psychological explanation for this mild delusion, it
would be that you had been hurt by some mistake of this kind early on and
have not quite learned to trust your skills afterwards. now, please note
that even if you don't like this explanation, you should not be surprised
when more experienced programmers naturally gravitate towards that kind
of explanation. as you get more experience, the silly bugs created by
using destructive operations in the wrong places should go away simply
because that's that experience is all about.
also, functional programming has enough problems being viewed as the
right thing that it does not need to be associated with needless creation
of garbage. that's the kind of laziness no programmer should be proud of.
if nothing else, I hope you understand why people "disagree" with your
view that this is about functional style.
#:Erik
> what people object to is the creation of garbage for no purpose, not the
> value of a more functional style. just as with any other resource, there
> is no excuse for wanton waste, but that's what you favor and you think
> the textbooks favor, but the latter is not quite so.
I don't object to that. Well, I do, but less than I object to the
Functional-Programming-Is-The-One-True-Way mantra. In particular the
f-p-i-t-o-t-w people will argue that if you are sufficiently smart you
can write compilers that avoid all that garbage creation by knowing
where you can actually be destructive behind the scenes, and also that
seriously ephemeral garbage is not so bad with modern GCs. I don't
even object to functional programming I think, I object to the
One-True-Way bit.
--tim
Gack! I hope nobody is really using such an idiom, push/reverse is
unnecessary, and awfully inefficient. I'm a loop user not a do user so I can't
give the code for doing this using do, but using loop:
(loop for item from 1 to 10
collect item)
which is no doubt desctructive, keeping a pointer to the end of
the list and using rplacd to add to the end of the list.
Sorry, but that idiom is extremely well entrenched. If you're not into
LOOP or Iterators/Collectors, and MAPCAR isn't convenient for the
particular use, that idiom is most common. I've seen and written it myself
many times, although I eventually learned LOOP.
Unless the resulting list is large, the inefficiency of NREVERSE probably
isn't an issue.
--
Barry Margolin, bar...@bbnplanet.com
GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
* "Christopher J. Vogt" <vo...@computer.org>
| Gack! I hope nobody is really using such an idiom, push/reverse is
| unnecessary, and awfully inefficient.
how did "actually faster" get contorted into "awfully inefficient"? you
have never tried to profile code like this, have you?
| I'm a loop user not a do user so I can't give the code for doing this
| using do, but using loop:
| (loop for item from 1 to 10
| collect item)
| which is no doubt desctructive, keeping a pointer to the end of the list
| and using rplacd to add to the end of the list.
sigh. you just made it harder for me to defend using LOOP. but at least
I know how to do it, which is why I prefer that LOOP do it for me...
SETF of CDR (or RPLACD if you insist) is a destructive operation, and
this has GC implications. PUSH does not have GC implications. the idiom
is (setf foo (setf (cdr foo) (cons bar nil))), and as you should be able
to see, this involves more work than (setf foo (cons bar foo)).
also note that it is unspecified whether NREVERSE does its work by CARs
or by CDRs. both have interesting performance characteristics.
note, however, that if you can convince the compiler to cons on the stack
(a.k.a., dynamic-extent), and not on the heap, REVERSE at the end might
be more efficient, since it can take advantage of the stack organization.
this, however, would require compiler recognition of this particular
idiom, and probably a waste of time to a vendor. so expect free software
to sport such an optimization opportunity. ;)
#:Erik
> >
> > (let ((acc nil))
> > (dolist (.....)
> > ......
> > (push item acc))
> > (nreverse acc))
>
> Gack! I hope nobody is really using such an idiom, push/reverse is
> unnecessary, and awfully inefficient. I'm a loop user not a do user so I can't
> give the code for doing this using do, but using loop:
> (loop for item from 1 to 10
> collect item)
> which is no doubt desctructive, keeping a pointer to the end of
> the list and using rplacd to add to the end of the list.
This is fine, if you have a loop. If you have some stuff which may
sometimes need to collect stuff, but isn't a loop, then PUSH/NREVERSE
is just fine. I not only use the idiom, I teach it. Believe me, you
don't want to teach people just learning lisp either how to keep tail
pointers by hand (might as well teach them C), or how to write a
COLLECTING macro (way too early to start dealing with
macro-defining-macros!), or that there is this magic COLLECTING thing
which solves the problem (destroys their model of how lists work, and
anyway is not in CL so they get confused about what is in the
language).
And although I have a COLLECTING macro that I use sometimes, I still
use PUSH/NREVERSE quite often, partly becaue my macro will only
collect one list at once and I often want to be splitting stuff into a
bunch of lists (OK, I could fix that, but the 8 line macro rapidly
becomes 100), but mostly because it's just a small constant factor at
best, and, actually, quite a lot of time stuff just isn't on the
critical path.
--tim
While I don't like push+nreverse myself, I cannot agree that this is
inefficient or that loop is faster. loop has to do very similar work
(destructive update) in this case, like you observe (remember that
nreverse can do its job in one pass). Whether this is done when an
element is collected or after all have been collected is IMHO much
more architecture- and cache- than language-dependent.
Regards,
Jorg Hohle
Telekom Research Center -- SW-Reliability
I'll wager that almost all LOOP and COLLECTING macros implement this by
maintaining a tail pointer and RPLACDing it as new elements are collected.
This is the type of thing that's a pain to write each time you do it, but
macro implementers are practically *expected* to do it for us. There are
very few architectures where this isn't the best way to do it; this
technique won't generate cdr-coded lists, but neither will the
push+nreverse.
But LOOP is in Common Lisp, so why don't you use/teach that?
> And although I have a COLLECTING macro that I use sometimes, I still use
> PUSH/NREVERSE quite often, partly becaue my macro will only collect one list
> at once and I often want to be splitting stuff into a bunch of lists
Sounds like you should be using CL's LOOP :-).
-- Don
--
Don Geddis ged...@cadabra.com (650) 508-7893 Fax (650) 508-7891
Cadabra Inc. 275 Shoreline Drive, Suite 505, Redwood Shores, CA 94065
Comparison Shopping: Smarter, Better, Faster http://cadabra.com
To be or not to be; that requires one TTL gate at a minimum, but you could do
it with three NAND gates, or just hook the output to Vcc.
The COLLECT feature of LOOP is nice until you want
something more complicated, like conditional collection,
which then introduces loop conditionals, and all language
hell breaks loose.
I use push/nreverse all over, but a better construct would
be welcomed. I haven't seen anything compelling to replace it.
One could just rename push/nreverse to collect/collected:
(macro collect (val var) `(setf {var} (cons {val} {var})))
(macro collected (var) `(setf {var} (nreverse {var})))
(loop with primes = nil
for i from 0 below 100
finally (return (collected primes))
do
(if (primep i)
then (collect i primes))
)
Which seems OK, but doesn't seem to really add that much clarity,
though I would much prefer it over using COLLECT in LOOP itself.
-Kelly Murray k...@niclos.com
For example, what I would like to say is
(let ((primes nil))
(dotimes (i 100)
(when (primep i)
(collect i primes)))
primes)
where collect is a constant-time operation that
collects values in the proper order.
You can write a collect macro with constant-time
proper order behavior that uses two places to
store results--the collected list and its tail.
The name of the second place can be computed at macro
expansion time from the first.
In the example above, collect would use
primes to store the list and primes-last to
store its tail. Now the biggest flaw in the
approach is that the user must declare the second
place. The code is exactly as above,
except for the added declaration of primes-last
(let ((primes nil) primes-last)
(dotimes (i 100)
(when (primep i)
(collect i primes)))
primes)
Not perfect, but close.
> Lieven Marchand wrote:
> > (let ((acc nil))
> > (dolist (.....)
> > ......
> > (push item acc))
> > (nreverse acc))
>
> Gack! I hope nobody is really using such an idiom, push/reverse is
> unnecessary, and awfully inefficient. I'm a loop user not a do user so I can't
> give the code for doing this using do, but using loop:
> (loop for item from 1 to 10
> collect item)
> which is no doubt desctructive, keeping a pointer to the end of
> the list and using rplacd to add to the end of the list.
>
Beauty is in the eye of the beholder but regarding efficiency:
did you try to time this? It's certainly not awfully inefficient.
USER(1): (declaim (optimize (safety 0 space 0 speed 3 debug 0)))
T
USER(2): (defvar *master-list* (loop for i from 1 to 10000 collect i))
*MASTER-LIST*
USER(4): (defun test-1 ()
(dotimes (i 100)
(let ((acc nil))
(dolist (item *master-list*)
(push (1+ item) acc))
(nreverse acc))))
TEST-1
USER(5): (compile 'test-1)
TEST-1
NIL
NIL
USER(6): (time (test-1))
; cpu time (non-gc) 410 msec user, 0 msec system
; cpu time (gc) 210 msec user, 0 msec system
; cpu time (total) 620 msec user, 0 msec system
; real time 645 msec
; space allocation:
; 1,000,000 cons cells, 0 symbols, 32 other bytes, 0 static bytes
NIL
USER(8): (defun test-2 ()
(dotimes (i 100)
(loop for item in *master-list*
collect (1+ item))))
TEST-2
USER(9): (compile 'test-2)
TEST-2
NIL
NIL
USER(10): (time (test-2))
; cpu time (non-gc) 310 msec user, 0 msec system
; cpu time (gc) 110 msec user, 0 msec system
; cpu time (total) 420 msec user, 0 msec system
; real time 428 msec
; space allocation:
; 1,000,100 cons cells, 0 symbols, 32 other bytes, 0 static bytes
NIL
So yes, in this implementation (Allegro CL 5.0 on Linux) LOOP is
faster but I had to augment the length of the list to 10000 to get
measurable differences.
An interesting difference is that the LOOP version uses one additional
cons to do its work.
A "collector" structure is be more useful for this, and very easy to
write (yeah, sometimes we cant use CL provided functions for EVERYTHING)
(defstruct collector
"Collect a list of objects easily and flexibly."
head
tail
)
(defmethod collect ((c collector) obj)
"Add a new object to the collector."
; Check if this is the first object
(if (collector-tail c)
; Add it to the entry after the tail
(progn
(setf (cdr (collector-tail c)) (list obj))
(setf (collector-tail c) (cdr (collector-tail c)))
)
; Make it the head and the tail
(progn
(setf (collector-tail c) (list obj))
(setf (collector-head c) (collector-tail c))
)
)
; Return the full list, in case this is the last form of a loop, in
which case the
; caller conveniently returns the list they were collecting.
(collector-head c)
)
(defun extract-symbols (list &aux (collector (make-collector)))
"Return a list of all the symbols in a list (a test function for the
collector)"
(declare (special collector))
(mapl
#'(lambda (lobj &aux (obj (car lobj))) (when (symbolp obj) (collect
collector obj)))
list
)
(collector-head collector)
)
; (extract-symbols '(a b 1.2 "string" c (ignore this) d)) => (A B C D)
; CU
; Dobes
But of course it's not for "no purpose". The purpose is to write correct code
first time round as often as possible. If you *really* want to avoid waste
write assembler, or indeed machine code :-)
Note that I didn't suggest that programs should be purely functional. I said
that in the normal course of things I use functional code rather that
destructive code first time round, all other things being equal. Then decide
where things can be modified for effeciency. Sometimes the code you writing
doesn't suit this style, because the data structures and algorithms you've
decided are naturally expressed in an imperative, destuctive fashion. (As it
happens I've spend most of this week writing code to shuffle bits around bit
vectors, because this is the only practical way to get the kind of performance
needed for the task in hand.)
Being "functional" is not a property of the world, it's a property of
mathematical abstractions of it. And for any reasonably rigourous "non-
functional" model there's an essentially equivalent functional model (subject
to suitable definitions).
This is getting a little away from the oringinal point, but since we're
there: what I'd quite like is a good higher-order relational language to
program in. With functional languages you get funtions and higher-order
quantification, with prolog and the like you get relations, but only first
order quantification.
> I'll wager that almost all LOOP and COLLECTING macros implement this by
> maintaining a tail pointer and RPLACDing it as new elements are collected.
> This is the type of thing that's a pain to write each time you do it, but
> macro implementers are practically *expected* to do it for us. There are
> very few architectures where this isn't the best way to do it; this
> technique won't generate cdr-coded lists, but neither will the
> push+nreverse.
>
However it does involve repeatedly destructively modifying objects
(rather than bindings like PUSH) which may be being tenured by the
garbage collector, leaving you with pionters from older generations
into newer ones, and thus I think, giving the GC more work to do.
I had at least one instance where I spent a long time doing something
like this and it ended up causing a lot of mysterious GC stuff to
happen and was actually not faster than what I did originally. OTOH
this was a while ago and I forget the fine details, so I may be just
wrong.
--tim
> In article <nkju2ur...@tfeb.org>, Tim Bradshaw wrote:
> > This is fine, if you have a loop. [...] there is this magic COLLECTING
> > thing which solves the problem (destroys their model of how lists work, and
> > anyway is not in CL so they get confused about what is in the language).
>
> But LOOP is in Common Lisp, so why don't you use/teach that?
Well, like it says, `this is fine if you have a loop'. Apart from
that, teaching LOOP to students starting CL is an insane idea: `here's
this language with a syntax you probably find a bit odd and
difficult. Oh, and by the way, there's this other embedded language
with a completely different syntax, which you have to learn too'.
--tim
that is supposed to be the consequence of successful training or
education. if you have to do something silly to get correct code, that
_should_ have been a very strong warning sign.
| If you *really* want to avoid waste write assembler, or indeed machine
| code :-)
idiot.
| Then decide where things can be modified for effeciency.
if you have no concept of what is or is not efficient before you start,
doing it afterwards will be very, very inefficient, in programmer time.
you appear from the above comment not to understand that "wanton waste"
is not restricted to what the computer does. I suggest you broaden your
perspective and appreciate that the whole idea of programming computers
is to reduce the total cost of the tasks the computer is set to do.
| Sometimes the code you writing doesn't suit this style, because the data
| structures and algorithms you've decided are naturally expressed in an
| imperative, destuctive fashion. (As it happens I've spend most of this
| week writing code to shuffle bits around bit vectors, because this is the
| only practical way to get the kind of performance needed for the task in
| hand.)
at least this looks like a redeeming quality, but I'm uncertain about how
you decide on data structures and algorithms to begin with.
in my view, functional programming is not usefully extended all the way
down (i.e., we don't want non-destructive instructions or memory, which
would have meant we'd need a new computer every billionth of a second),
so clearly there's a point where the exported interface is functional and
the implementation is not. I extend this view to functions at any level:
functional programming is about a design style that _exports_ a clean
interface, but I should _not_ care what it does inside. consequently, if
I design a function with a clean interface, it matters not whether I use
a "dirty" technique or not inside it as long as nobody is impacted by it.
this, incidentally, leads to why I favor a more functional programming
style: I don't have to deal with any "ownership protocol" of objects that
have been allocated, but when I do know that I alone own an object, I
have no compulsions about modifying it, or asking functions I call to
modify it. and I _don't_ see this as betraying the Functional Style.
#:Erik
>
> Being "functional" is not a property of the world, it's a property of
> mathematical abstractions of it. And for any reasonably rigourous "non-
> functional" model there's an essentially equivalent functional model (subject
> to suitable definitions).
Yes, of course, but that model may be much harder to use, and much
further from people's intuitions. Sufficiently hard and far that for
most purposes it's useless.
--tim
Well, this depends very much on poeple's intuitions work; which is partly
dependent on the way they've been taught. If you teach people functional
programming from the start then they'll tend to have intuitions that fit in
with this. If they've been brought up on Pascal, Basic and C then they're
likley to initially struggle with functional models of problem areas.
I think it's a lot like learning category theory after having had a fairly
traditional mathematical education: very little fits well with your
intuitions; but in the long run it's worth the effort because you get a new
perspective on your existing knowledge.
Kelly> I think we've gone through this before...
Kelly> The COLLECT feature of LOOP is nice until you want
Kelly> something more complicated, like conditional collection,
Kelly> which then introduces loop conditionals, and all language
Kelly> hell breaks loose.
CMUCL has a COLLECT macro:
Macro documentation:
Collect ({(Name [Initial-Value] [Function])}*) {Form}*
Collect some values somehow. Each of the collections specifies a bunch of
things which collected during the evaluation of the body of the form. The
name of the collection is used to define a local macro, a la MACROLET.
Within the body, this macro will evaluate each of its arguments and collect
the result, returning the current value after the collection is done. The
body is evaluated as a PROGN; to get the final values when you are done, just
call the collection macro with no arguments.
Initial-Value is the value that the collection starts out with, which
defaults to NIL. Function is the function which does the collection. It is
a function which will accept two arguments: the value to be collected and the
current collection. The result of the function is made the new value for the
collection. As a totally magical special-case, the Function may be Collect,
which tells us to build a list in forward order; this is the default. If an
Initial-Value is supplied for Collect, the stuff will be rplacd'd onto the
end. Note that Function may be anything that can appear in the functional
position, including macros and lambdas.
Perhaps this is closer to what people are looking for?
Ray
> In many idiomatic uses of destructive operations, there isn't any
> effort to be invested. For instance,
>
> (let ((acc nil))
> (dolist (.....)
> ......
> (push item acc))
> (nreverse acc))
>
> is an idiomatic way to collect some stuff together. NREVERSE is
> destructive but the function this is part of is still purely
> functional since it only destructively modifies things it has itself
> created.
This is what Paul Graham calls "functional behavior" in his book "On Lisp".
I don't have it handy, but I seem to remember that there's a chapter with a
similar title.
Paolo
--
Paolo Amoroso <amo...@mclink.it>
> Well, this depends very much on poeple's intuitions work; which is partly
> dependent on the way they've been taught. If you teach people functional
> programming from the start then they'll tend to have intuitions that fit in
> with this. If they've been brought up on Pascal, Basic and C then they're
> likley to initially struggle with functional models of problem areas.
>
Unfortunately most people develop a world model quite a long time
before they learn to program!
--tim
> The COLLECT feature of LOOP is nice until you want
> something more complicated, like conditional collection,
> which then introduces loop conditionals, and all language
> hell breaks loose.
> I use push/nreverse all over, but a better construct would
> be welcomed. I haven't seen anything compelling to replace it.
You haven't followed comp.lang.lisp closely enough :-)
Tim Bradshaw posted the following:
From: Tim Bradshaw <t...@aiai.ed.ac.uk>
Subject: Re: Append object to list - Question
Date: 05 Oct 1998 12:25:37 +0100
Message-ID: <ey3af3b...@wiay.aiai.ed.ac.uk>
I tend to use this thing, that I wrote, but stole I'm sure from
somewhere else (possibly Interlisp?). It probably needs to be
generalised somehow, not to mention cleaned up (all those dollar
signs...), but it's quite useful, especially if you want to collect
things over several loops or something.
(defmacro collecting (&body forms)
;; Collect some random stuff into a list by keeping a tail-pointer
;; to it, return the collected list. No real point in using
;; gensyms, although one probably should on principle.
"Collect things into a list forwards. Within the body of this macro
The form `(COLLECT THING)' will collect THING into the list returned by
COLLECTING. Uses a tail pointer -> efficient."
(let (($resnam$ (gensym)) ($tail$ (gensym)) ($thing$ (gensym)))
`(let
(,$resnam$ ,$tail$)
(macrolet
((collect
(thing)
;; Collect returns the thing it's collecting
`(let ((,',$thing$ ,thing))
(if ,',$resnam$
(setf (cdr ,',$tail$)
(setf ,',$tail$ (list ,',$thing$)))
(setf ,',$resnam$
(setf ,',$tail$ (list ,',$thing$))))
,',$thing$)))
,@forms)
,$resnam$)))
--tim
Also, Bruno Haible <hai...@ilog.fr> posted an almost identical
COLLECTING macro to the same effect some month ago. Deja News yields:
Subject: Re: Simplified LOOP
Author: Bruno Haible <hai...@ilog.fr>
Date: 1998/02/06
Message-ID: <6bg6a8$n3v$1...@nz12.rz.uni-karlsruhe.de>
(defmacro collecting (&body forms)
(let ((a (gensym))
(b (gensym))
(c (gensym)))
`(LET ((,a NIL) (,b NIL))
(MACROLET ((COLLECT (FORM)
`((LAMBDA (,',c)
(IF ,',a
(SETF (CDR ,',b) (SETF ,',b ,',c))
(SETF ,',a (SETF ,',b ,',c))
) )
(LIST ,FORM)
)
))
(PROGN ,@forms)
,a
) )
) )
(defmacro collect (form)
(error "collect used outside of collecting")
)
Also, while peeking at the CMUCL sources, I found in their code/PCL/
subdirectory a very interesting collection of iterator/collecting
macros which you could use as well.
The advantage of all these COLLECT forms is that thay can appear
lexically anywhere deep inside the collecting form -- unlike LOOP
clauses as you noticed.
>... Now the biggest flaw in the
>approach is that the user must declare the second
>place. The code is exactly as above,
>except for the added declaration of primes-last
> (let ((primes nil) primes-last)
> (dotimes (i 100)
> (when (primep i)
> (collect i primes)))
> primes)
>Not perfect, but close.
Something easy that I've implemented several times is
(with-collector collect-prime
(dotimes (i 100)
(when (primep i)
(collect-prime i))))
The return value of with-collector is the collected
list, same as with-output-to-string.
Still, I've never seen anyone have trouble maintaining
(loop for i from 1 to 100
when (primep i)
collect i)
*and* I bet more novices really meant 1 to 100 when
writing the latter and didn't mean 0 to 99 in the former.
> I also have reservations about the loop syntax
> and want a convenient way of collecting values
> other than loop or the push/nreverse idiom.
>
> For example, what I would like to say is
> (let ((primes nil))
> (dotimes (i 100)
> (when (primep i)
> (collect i primes)))
> primes)
> where collect is a constant-time operation that
> collects values in the proper order.
what's so wrong with the standard idiom?
(let ((primes nil))
(dotimes (i 100)
(when (primep i)
(push i primes)))
(nreverse primes))
it *looks* just fine to me. there's no accounting for taste, however,
and perhaps it doesn't appeal to your sense of aesthetics. be that as
it may, the only complaint i can see is the efficiency aspect. this
side of predlaring an array, push is about as cheap as you're ever
going to get. is nreverse *that* expensive?
sometimes you do not need the nreverse, then you can just push. this
happens when
1) you just don't care about the order of the result.
2) you can have the loop run backwards (in the primes
collection case, count backwards from 100 downto 1).
3) doing two `push without reverse fix-up' operations in a row
restores the original order.
i guess all this is obvious. i just don't understand the dislike of
push/nreverse in the first place.
--
johan kullstam
Well, judging by the C/C++ example, Lisp will never take over the
world if people don't spend vast amounts of time micro-optimising
their programs. So this is definitely to be encouraged!
--tim
I looked at the CMUCL PCL/iterate macro and found it quite interesting.
So I looked on the net and found the Jonathan Amsterdam iterate macro
which look even more interesting.
I have some questions about it:
Has anybody used it?
Is the 1.2 version the latest?
It looks like it's more powerful than loop. Any comments on this?
Efficiency?
Are there other macro packages like this you know of?
Thanks,
Marc Battyani
I should probably let this drop now, but I'll have one more go.
I'd guess that people tend to have small models of various parts of the world,
and at the points of overlap they may often be inconsistent. Any particular
program or part thereof that attempt to model some really small part of the
real world may be built using intuitions that don't need to be to cope able
(and in practice can't anway) with larger things.
IMO it helps to expose yourself to lots of different kinds of techniques for
writing different code. You learn new and productive ways of building
intuitions about different problem areas that are in part forced on you by the
limitations of the techniques involved.
Writing (for example) purely functional code is hard if you haven't had any
pratcice at it, but then so is e.g. writing in prolog if you have no experiece
of that.
pardon the sarcasm, but if you value learning so much, how come you
haven't learned to write code that doesn't waste resources, yet, and
instead want others to "learn" your wasteful ways?
#:Erik, still not opposed to functional style, but opposed to wanton waste
> I'd guess that people tend to have small models of various parts of
> the world, and at the points of overlap they may often be
> inconsistent. Any particular program or part thereof that attempt to
> model some really small part of the real world may be built using
> intuitions that don't need to be to cope able (and in practice can't
> anway) with larger things.
I think this is my last point about this too.
I really think it's pretty important not to ignore the models people
have. They may be for `small' domains, but they typically work pretty
well. If I was writing software to, say, help mechanical engineers
design bridges, and I came up to these people and told them `hey, your
model of how bridges work is all crap, you should do this other thing
that I've invented', then they wouldn't hire me. And they'd be
*right* not to hire me: their models work pretty well and build some
pretty good bridges, and have seen almost 100yrs of development and
testing, which is more than you can say for any software `engineering'
technique, be it FP or OOP or whatever. The track record of people out
in the real world is kind of impressive compared to software people,
actually, they do this impressive stuff like sending people to the
moon while we grovel in the dirt trying to get our OS's to stay up for
a day or so.
I'm not against FP in some domains (some theories of physics would
suit it to a tee), or to a generally functional leaning (which I have,
I'm a Lisp person after all), I just think that in many case software
people should be listening a lot more closely to the real engineers
who make real things with such brilliant success.
--tim