Is there an easier way to convert a hash table into a list than this? If not easier, how about an alternative where it could be done already in the LET declaration?
(let (hash-list) (maphash #'(lambda (key val) (push val hash-list)) hash-table)) -- Do you agree that it takes over seven minutes for sunlight to get to Earth?
Hannu Koivisto <az...@iki.fi.ns> writes: > Ola Rinta-Koski <ola.rinta-ko...@iki.fi.no.spam.today> writes: > | Is there an easier way to convert a hash table into a list than > Values of a hash table, based on your example, right?
Yes, sorry for not being more clear.
> | this? If not easier, how about an alternative where it could be done > | already in the LET declaration?
> (let ((value-list (loop for i being each hash-value of hash-table > collect i))) > ...)
> Is this what you had in mind?
Indeed, thanks a lot! I am not too fond of the LOOP macro in general, but if it does the job at least somewhat readably, then I'm willing to tolerate it, as in this case. -- Get this: Do they want to be the first on their block to dress in revealing lingerie in the foreseeable future, please?
Ola Rinta-Koski <ola.rinta-ko...@iki.fi.no.spam.today> writes: > I am not too fond of the LOOP macro in general, but if it does the > job at least somewhat readably, then I'm willing to tolerate it, as > in this case.
What job is that? What do you mean LOOP gives you here other than some (quite horrible, IMHO) artificially sweetened syntax of exactly the same as your LET-form?
If you for some reason don't want to see the temporary collecting variable, how about wrapping it in a special purpose macro, like this:
Where COLLECTING is defined as (there are other versions of this macro around, this is mine. I apologise for the style, I wrote it a long time ago):
(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$)))
> Ola Rinta-Koski <ola.rinta-ko...@iki.fi.no.spam.today> writes: > > I am not too fond of the LOOP macro in general, but if it does the > > job at least somewhat readably, then I'm willing to tolerate it, as > > in this case. > What job is that? What do you mean LOOP gives you here other than some > (quite horrible, IMHO) artificially sweetened syntax of exactly the > same as your LET-form?
It is, I think, a bit more readable. Neither is particularly elegant. -- Did you ever wonder why the KGB is out to get him?
* Frode Vatvedt Fjeld <fro...@acm.org> | What do you mean LOOP gives you here other than some (quite horrible, | IMHO) artificially sweetened syntax of exactly the same as your LET-form?
I'm curious about all this energy spent hating LOOP and reinventing silly wheels, not so much because I think LOOP is a good solution to a problem that does not seem to have any better solutions, but because I think it shows how the need for patterns might evolve. all the time people whine about LOOP, they produce a _lot_ of code to supplant simple expressions.
for some bizarre reasons that have nothing to do with just getting the job done or correctness or speed of execution or any of the numerous other reasons that are actually valid concerns for rewriting code, LOOP forms just _have_ to be rewritten, whined about, and detested. offering to take its place is just a bunch of _new_ weirdness that the author is sure looks a lot better, but it involves the pattern argument in a strong sense: the author can read his own "pattern-oriented" code and claims, directly or indirectly, not to be able to read LOOP forms. this seems very strange to me, but I have the same reaction to the IF* macro: if I can't macroexpand this abomination into the COND clauses it "really is", my head just hurts looking at it, so something I should be able to see is at work here. yet I don't see it.
what is it about some forms of expression that cry out for rewrites and which encourages us to start over with something that somebody else must have thought was much worse than their cure? why is our cure better?
this leads to my question: is the pattern business just a means of saying "it's OK to retch at this code, but don't rewrite it -- it's the best we have around", sort of like saying that all the other cures that people ache to deply will look no better than the pattern-oriented solution?
(yeah, yeah, I'm aware that patterns are supposed to be abstract, but they wind up being implemented by duplicated, verbose code, regardless.)
* Erik Naggum wrote: > for some bizarre reasons that have nothing to do with just getting the > job done or correctness or speed of execution or any of the numerous > other reasons that are actually valid concerns for rewriting code, LOOP > forms just _have_ to be rewritten, whined about, and detested. offering > to take its place is just a bunch of _new_ weirdness that the author is > sure looks a lot better, but it involves the pattern argument in a strong > sense: the author can read his own "pattern-oriented" code and claims, > directly or indirectly, not to be able to read LOOP forms.
I wasn't one of those claiming not to be able to read LOOP, but my alternative COLLECTING thing is something I use a fair amount in places where really something is going on that LOOP won't do. Typical examples are where I'm walking recursively over some arbitrary data structure and just occasionally need to collect some interesting thing. Whether the trick of building the list forward is worth it (over the more usual push + nreverse type solution) is another question.
Where BIG-RECURSION does (funcall #'collect thing) at various points. Again, this may not get you anything over push/nreverse but it does do things that LOOP doesn't.
I'd never actually use COLLECTING in the simple loop case though: I *like* LOOP!
In article <3153828708572466...@naggum.no>, Erik Naggum <e...@naggum.no> wrote: > for some bizarre reasons that have nothing to do with just getting the > job done or correctness or speed of execution or any of the numerous > other reasons that are actually valid concerns for rewriting code, LOOP > forms just _have_ to be rewritten, whined about, and detested. offering > to take its place is just a bunch of _new_ weirdness that the author is > sure looks a lot better
Ironically, it may be a fundamental feature of Lisp that encourages this sort of behavior. One of the great strengths of the language is that it makes it easy to do incremental compilation, which results in short debug cycles, which makes it very tempting to do just one more iteration to get it Right with a capital R. Since every iteration is cheap it's very insidious -- you can fritter away the whole day on some useless piece of art before you realize it. C/C++/Java programmers never get caught in this because they know a priori that if they even *try* to make an existing thing better it will take them the whole day, so they don't start. Instead, they accept the existing code base cruft and all and move on. This is why they win.
Erik Naggum <e...@naggum.no> writes: > for some bizarre reasons that have nothing to do with just getting > the job done or correctness or speed of execution or any of the > numerous other reasons that are actually valid concerns for > rewriting code, LOOP forms just _have_ to be rewritten, whined > about, and detested.
Well, I disagree with you here. I think "(hash-table-list x)" is a lot more readable (and not the least bit weird) than the loop expression, which I find about equally readable to the "plain" lisp form. And, more importantly, the time-to-convince-me-it's-correct factor is also much better.
I'm a bit confused: My impression after reading OnLisp and other books is that I'm encouraged to write and use precisely this kind of "utilities". Do you disagree with this?
Humm. I just define a function (hash-table-values ht) and use that. Then whatever ugly or elegant code I use to get the actual values is just written out once.
By the way, I think it would be useful to collect some of these lists of different ways to do things, add some discussion of the ways, and make them available to people wanting to learn Lisp (e.g. by putting them on Web pages).
So far no one has suggested using with-hash-table-iterator.
Erik Naggum <e...@naggum.no> writes: > I'm curious about all this energy spent hating LOOP and reinventing silly > wheels, not so much because I think LOOP is a good solution to a problem > that does not seem to have any better solutions, but because I think it > shows how the need for patterns might evolve. all the time people whine > about LOOP, they produce a _lot_ of code to supplant simple expressions.
A curious thing is that when books try to say what's bad about LOOP (and I think even one of Graham's does this), they give some of the least clear examples -- even though in some cases, LOOP is as clear as anything and clearer than most alternatives. For instance DOTIMES handles some things quite well; but if you depart from the 0 upto n-1 case, LOOP is usually better.
* Frode Vatvedt Fjeld <fro...@acm.org> | Well, I disagree with you here. I think "(hash-table-list x)" is a lot | more readable (and not the least bit weird) than the loop expression, | which I find about equally readable to the "plain" lisp form. And, more | importantly, the time-to-convince-me-it's-correct factor is also much | better.
well, I would _not_ understand what a function called HASH-TABLE-LIST was doing without reading its documentation or source code, and that I would have to repeat every time I found it in a new package or project. the reason is quite simple: there are so many lists that could be generated from hash tables that none of them merit being the only one thus named.
| I'm a bit confused: My impression after reading OnLisp and other books is | that I'm encouraged to write and use precisely this kind of "utilities". | Do you disagree with this?
actually, yes. naming utility functions is a very hard problem, and they should therefore not be multiplied lightly. when there is clearly a diversity of needs, it is better to be a little more verbose than to pollute the name space with thousands of functions. the C++ way to do this is to talk about patterns to discourage people from creating tons of one-call functions. the Lisp way is to create sub-languages and macros that cover _multiple_ needs well. I actually think LOOP succeeds in this, but it suffers in my view from not creating an environment with local macros instead of the positional "lexical keyword" stuff that I have come to dislike in other languages.
Erik Naggum <e...@naggum.no> writes: > | I'm a bit confused: My impression after reading OnLisp and other books is > | that I'm encouraged to write and use precisely this kind of "utilities". > | Do you disagree with this?
> actually, yes. naming utility functions is a very hard problem, and they > should therefore not be multiplied lightly. when there is clearly a > diversity of needs, it is better to be a little more verbose than to > pollute the name space with thousands of functions.
I strongly agree here. The problem is that Graham's books and Norvig's books do this for like every single program and encourage it. I think it just makes reading the code ten times more of a PITA as you have to set up a tags table and constantly peck away at M-. so you can follow the abstraction's function-calls back to the source and memorize what they do before continuing. Here are some of the "useful abstractions" you can find in both the PAIP and AI:AMA code from Norvig, for example:
(defun length>1 (list) "Is this a list of 2 or more elements?" (and (consp list) (cdr list)))
(defun length=1 (list) "Is this a list of exactly one element?" (and (consp list) (null (cdr list))))
(defun starts-with (list element) "Is this a list that starts with the given element?" (and (consp list) (eq (first list) element)))
(defun last1 (list) "Return the last element of a list." (first (last list)))
(defun left-rotate (list) "Move the first element to the end of the list." (append (rest list) (list (first list))))
(defun right-rotate (list) "Move the last element to the front of the list." (append (last list) (butlast list)))
These kind of things get REALLY ANNOYING after awhile....
> > | I'm a bit confused: My impression after reading OnLisp and other books is > > | that I'm encouraged to write and use precisely this kind of "utilities". > > | Do you disagree with this?
> > actually, yes. naming utility functions is a very hard problem, and they > > should therefore not be multiplied lightly. when there is clearly a > > diversity of needs, it is better to be a little more verbose than to > > pollute the name space with thousands of functions.
> I strongly agree here. The problem is that Graham's books and Norvig's > books do this for like every single program and encourage it. I think > it just makes reading the code ten times more of a PITA as you have to > set up a tags table and constantly peck away at M-. so you can follow > the abstraction's function-calls back to the source and memorize what > they do before continuing. Here are some of the "useful abstractions" > you can find in both the PAIP and AI:AMA code from Norvig, for example:
> (defun length>1 (list) > "Is this a list of 2 or more elements?" > (and (consp list) (cdr list)))
> (defun length=1 (list) > "Is this a list of exactly one element?" > (and (consp list) (null (cdr list))))
> (defun starts-with (list element) > "Is this a list that starts with the given element?" > (and (consp list) (eq (first list) element)))
> (defun last1 (list) > "Return the last element of a list." > (first (last list)))
> (defun left-rotate (list) > "Move the first element to the end of the list." > (append (rest list) (list (first list))))
> (defun right-rotate (list) > "Move the last element to the front of the list." > (append (last list) (butlast list)))
> These kind of things get REALLY ANNOYING after awhile....
I think all of the above are both useful and clear, and I define and use similar functions myself. If I saw, for instance, the (append (rest ...) (list (first same...)) in-line, I'd have to take time to work out what it did, especially if the "..." were replaced by something nontrivial. Left-rotate, on the other hand, is instantly recognisable.
> > | I'm a bit confused: My impression after reading OnLisp and other books is > > | that I'm encouraged to write and use precisely this kind of "utilities". > > | Do you disagree with this?
> > actually, yes. naming utility functions is a very hard problem, and they > > should therefore not be multiplied lightly. when there is clearly a > > diversity of needs, it is better to be a little more verbose than to > > pollute the name space with thousands of functions.
> I strongly agree here. The problem is that Graham's books and Norvig's > books do this for like every single program and encourage it. I think > it just makes reading the code ten times more of a PITA as you have to > set up a tags table and constantly peck away at M-. so you can follow > the abstraction's function-calls back to the source and memorize what > they do before continuing.
Yes, the balance is often lost. It is so easy (and fun) to personalize lisp that if you're not careful, no one but the author will know what is going on. AIMA was very difficult to follow because of that.
>Here are some of the "useful abstractions" > you can find in both the PAIP and AI:AMA code from Norvig, for example:
> (defun length>1 (list) > "Is this a list of 2 or more elements?" > (and (consp list) (cdr list)))
> (defun length=1 (list) > "Is this a list of exactly one element?" > (and (consp list) (null (cdr list))))
> (defun starts-with (list element) > "Is this a list that starts with the given element?" > (and (consp list) (eq (first list) element)))
> (defun last1 (list) > "Return the last element of a list." > (first (last list)))
> (defun left-rotate (list) > "Move the first element to the end of the list." > (append (rest list) (list (first list))))
> (defun right-rotate (list) > "Move the last element to the front of the list." > (append (last list) (butlast list)))
> These kind of things get REALLY ANNOYING after awhile....
> Christopher
Interesting. While I agreed with your general point i think your choice of examples weakens it. All of the above utilities are very clearly and appropriately named and supply basic functionality that is very commonly needed. (well, with the possible exception of the rotate functions, which aside from school exercises, i have not ever needed yet)
There is definately a worthwhile gain in readabiltiy and writability here. I frequently use a number of those utilities plus a dozen or so similar of my own.
The points made above still stand, however. *Everything* in moderation.
> In article <3153828708572466...@naggum.no>, Erik Naggum <e...@naggum.no> wrote:
> > for some bizarre reasons that have nothing to do with just getting the > > job done or correctness or speed of execution or any of the numerous > > other reasons that are actually valid concerns for rewriting code, LOOP > > forms just _have_ to be rewritten, whined about, and detested. offering > > to take its place is just a bunch of _new_ weirdness that the author is > > sure looks a lot better
Well, I have yet to see anyone else voice this opinion, but it is mine...
Jeff Dalton wrote: > cba...@2xtreme.net (Christopher R. Barry) writes: > > Here are some of the "useful abstractions" > > you can find in both the PAIP and AI:AMA code from Norvig, for example:
[examples elided]
> > These kind of things get REALLY ANNOYING after awhile.... > I think all of the above are both useful and clear, and I define and > use similar functions myself. If I saw, for instance, the (append > (rest ...) (list (first same...)) in-line, I'd have to take time to > work out what it did, especially if the "..." were replaced by > something nontrivial. Left-rotate, on the other hand, is instantly > recognisable.
It is certainly true that it takes some time to analyze the snippet. I usually define every utility in a FLET first, or more likely just use an extra line in a LET or LOOP environment to _name_ some interim result so as not to have to recognise and analyse the snippet. I make it a global function when it becomes a PITA to write it several times, but I found I waste less time this way than finding out the name of a globally defined utility and adjusting it when I want to use it the second time. It's easier for me to make the proper generalization after I have gotten my hands dirty at some cases.
It could lead to better optimization (e.g., inlining, type assertions or something even more drastic), but it's another story.
Speaking of FLETs and LETs: what's the best way of testing them? Sometimes I just copy the snippet and put a DEFUN or SETF wrapper around it, but there has to be a better way. As for LET statements, my debugger shows the bindings for some, but not necessarily all lexical variables (at (debug 3) of course), but I can not invoke a FLET.
Here's a wishlist: at (debug 3), be able to
- access _all_ lexical bindings - invoke local functions - restart and continue with every stack frame
Maybe such an animal exists, I just haven't found or tamed it. (Luckily, I am not in the debugger too often :-) What are the best practices?
> Speaking of FLETs and LETs: what's the best way of testing them? > Sometimes I just copy the snippet and put a DEFUN or SETF wrapper around > it, but there has to be a better way.
> As for LET statements, my debugger shows the bindings for some, but > not necessarily all lexical variables (at (debug 3) of course), but > I can not invoke a FLET.
> Here's a wishlist: at (debug 3), be able to
> - access _all_ lexical bindings > - invoke local functions > - restart and continue with every stack frame
> Maybe such an animal exists, I just haven't found or tamed it.
Looks like you want a Symbolics. You can do all of the above. To invoke a local function though, you need to be in a debugger stack frame where the local function is in scope. (There's not some magic function of macro you can use to say "give me the flet-function named FOO in global function BAR or anything like that, if that's what you were thinking of.) In the Genera debugger, when you evaluate a form it is by default evaluated in the environment of the program you are debugging. For example, if you do:
> (Luckily, I am not in the debugger too often :-) What are the best > practices?
When not using Genera I generally use debug-print macros instead of the debugger, though Allegro CL has got a pretty good debugger. (LispWorks seems to as well from browsing the doc, but I haven't used it much.) With (DEBUG 3) and (SPEED 0) you still don't seem to get all of the locals with their names (ACL 5.0.1, Linux/X86) many times, but debug-printing will always give you that info....
> I think all of the above are both useful and clear, and I define and > use similar functions myself. If I saw, for instance, the (append > (rest ...) (list (first same...)) in-line, I'd have to take time to > work out what it did, especially if the "..." were replaced by > something nontrivial.
This is what commenting is for. If you use the same procedure A LOT throughout a program then sure, make a global function. Else, I think flets or just coding in place with commenting is better. Note that even with INLINE declarations (which a lot of people seem to be saying lately that most CL's don't honor...), there are performance impacts to be wary of when using these small global functions in certain implementations and contexts.
For example, I once wrote a program that would read a large file in a line at a time and do some pretty compute-intensive stuff. Part of it looked like this:
(loop for start = (position-if #'(lambda (char) <blah>) ...) ...)
I later realized that I had duplicated the (LAMBDA (CHAR) <blah>) elsewhere so I created a global function and replaced both the lambdas with it and performance went from around 8 seconds to 12 seconds! (This was with Allegro CL 5.0.1 Linux/X86 if anyone cares. I didn't try with CMU CL but should of....) This was a real eye-opener I thought. (And needless to say I left the lambdas as they were.)
On 10 Dec 1999 15:31:48 +0000, Erik Naggum <e...@naggum.no> wrote:
> * Frode Vatvedt Fjeld <fro...@acm.org> > | What do you mean LOOP gives you here other than some (quite horrible, > | IMHO) artificially sweetened syntax of exactly the same as your LET-form?
> I'm curious about all this energy spent hating LOOP and reinventing silly > wheels, not so much because I think LOOP is a good solution to a problem > that does not seem to have any better solutions, but because I think it > shows how the need for patterns might evolve. all the time people whine > about LOOP, they produce a _lot_ of code to supplant simple expressions.
I'm pretty sure this will fuel passions and prejudices about LOOP, but FTR LispWorks' SQL interface extends LOOP to work over database query results, e.g.
(loop for (name age . rest) being each record in "select * from person" initially (format t "~%~20A ~5A" "NAME" "AGE") count id into people sum age into total do (format t "~%~20A ~5D" name age) finally (format t "~2%AVERAGE AGE = ~D" (/ total people)))
And voila, LOOP as database reporting language! :-j
__Jason
PS The macroexpansion uses a database cursor to iterate over the results rather than secretly cons'ing a list and then iterating over that.
Jason Trenouth <ja...@harlequin.com> writes: > On 10 Dec 1999 15:31:48 +0000, Erik Naggum <e...@naggum.no> wrote:
> > * Frode Vatvedt Fjeld <fro...@acm.org> > > | What do you mean LOOP gives you here other than some (quite horrible, > > | IMHO) artificially sweetened syntax of exactly the same as your LET-form?
> > I'm curious about all this energy spent hating LOOP and reinventing silly > > wheels, not so much because I think LOOP is a good solution to a problem > > that does not seem to have any better solutions, but because I think it > > shows how the need for patterns might evolve. all the time people whine > > about LOOP, they produce a _lot_ of code to supplant simple expressions.
> I'm pretty sure this will fuel passions and prejudices about LOOP, but FTR > LispWorks' SQL interface extends LOOP to work over database query results, e.g.
> (loop for (name age . rest) > being each record in "select * from person" > initially (format t "~%~20A ~5A" "NAME" "AGE") > count id into people > sum age into total > do (format t "~%~20A ~5D" name age) > finally (format t "~2%AVERAGE AGE = ~D" (/ total people)))
> And voila, LOOP as database reporting language! :-j
This is cool. What is the complete syntax of the extension? Is it just
each RECORD in <SQL-QUERY-AS-STRING> and each RECORD in <SQL-QUERY-AS-SEXP>
?
Cheers
-- 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
> > (loop for (name age . rest) > > being each record in "select * from person" > > initially (format t "~%~20A ~5A" "NAME" "AGE") > > count id into people > > sum age into total > > do (format t "~%~20A ~5D" name age) > > finally (format t "~2%AVERAGE AGE = ~D" (/ total people)))
> > And voila, LOOP as database reporting language! :-j
> This is cool. What is the complete syntax of the extension? Is it > just
> each RECORD in <SQL-QUERY-AS-STRING> > and > each RECORD in <SQL-QUERY-AS-SEXP>
> ?
> Cheers
The syntax is defined as:
{for|as} var [type-spec] being {the|each}{tuples|tuple} {in|of} query-expression
(but I think you can use "record" as well as "tuple"). And "query-expression" can use the symbolic-SQL expression syntax.
(let ((temp (some long expression))) (and (consp temp) (cdr temp)))
and because I *know* it is more efficient than what would be used otherwise:
(> (length (some long expression)) 1)
One thing that makes tastes vary is the environment in which one is served. I'm used to an editing environment in which, when the cursor is placed at the "_" in
(length>1 _ (some long expression))
one automatically sees "length>1 (list): Is this a list of 2 or more elements?" in the mode line, with no need to have to hit M-., no need to move about in different buffers, and with the tags table set up for you automatically. I agree that reading paper printouts can be hard, but for online reading, I never had a problem with this style, for my code or for others.
> > | I'm a bit confused: My impression after reading OnLisp and other books is > > | that I'm encouraged to write and use precisely this kind of "utilities". > > | Do you disagree with this?
> > actually, yes. naming utility functions is a very hard problem, and they > > should therefore not be multiplied lightly. when there is clearly a > > diversity of needs, it is better to be a little more verbose than to > > pollute the name space with thousands of functions.
> I strongly agree here. The problem is that Graham's books and Norvig's > books do this for like every single program and encourage it. I think > it just makes reading the code ten times more of a PITA as you have to > set up a tags table and constantly peck away at M-. so you can follow > the abstraction's function-calls back to the source and memorize what > they do before continuing. Here are some of the "useful abstractions" > you can find in both the PAIP and AI:AMA code from Norvig, for example:
> (defun length>1 (list) > "Is this a list of 2 or more elements?" > (and (consp list) (cdr list)))
Jason Trenouth <ja...@harlequin.com> writes: > On 10 Dec 1999 15:31:48 +0000, Erik Naggum <e...@naggum.no> wrote:
> I'm pretty sure this will fuel passions and prejudices about LOOP, but FTR > LispWorks' SQL interface extends LOOP to work over database query results, e.g.
Well, I'm not sure that fuels passions. }:) My main objections to LOOP (which, NB, I do use but prefer other forms whenever practical) are the un-Lisp syntax and the overloading of familiar symbols like if and and. Make it look and behave like Lisp and I'd like it.
If it easily integrates database handling, well, good.