From: Zach Beane
Subject: tricks that made you grin or grimace
Date: 
Message-ID: <m37iwn8cro.fsf@unnamed.xach.com>
Have you seen any snippet of Lisp that made you do a double-take, work
out what's happening, and then groan or smile? And thought "I'll have
to use that myself some day"?

Here are a couple that I came across recently:

   (defun string-repeated (string count)
     (format nil ···@{~A~:*}" count string))

   (defun eeql (&rest args)
     "As (= 5 5 5) => t, so too (eeql 'foo 'foo 'foo) => t"
     (every #'eql args (cdr args)))

How about yours?

The only rule: should be concise. If it requires some non-concise
external functionality, just use a name for the external thing that
makes its behavior obvious (e.g. CIRCULAR-LIST).

Zach

From: Pillsy
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166587969.138055.151710@f1g2000cwa.googlegroups.com>
Zach Beane wrote:
[...]
> How about yours?

> The only rule: should be concise. If it requires some non-concise
> external functionality, just use a name for the external thing that
> makes its behavior obvious (e.g. CIRCULAR-LIST).

It's evidently very well-known, but it still kind of bent my head into
a new shape when I first saw it.

(defun transpose (matrix)
  "Transposes a list of lists as if it were a matrix, so that ((1 2) (3
4) (5 6)) => ((1 3 5) (2 4 6))"
  (apply #'mapcar (cons #'list matrix)))

Cheers, Pillsy
From: Kaz Kylheku
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166596244.137477.179220@80g2000cwy.googlegroups.com>
On Dec 19, 8:12 pm, "Pillsy" <·········@gmail.com> wrote:
> Zach Beane wrote:[...]
>
> > How about yours?
> > The only rule: should be concise. If it requires some non-concise
> > external functionality, just use a name for the external thing that
> > makes its behavior obvious (e.g. CIRCULAR-LIST).It's evidently very well-known, but it still kind of bent my head into
> a new shape when I first saw it.
>
> (defun transpose (matrix)
>   "Transposes a list of lists as if it were a matrix, so that ((1 2) (3
> 4) (5 6)) => ((1 3 5) (2 4 6))"
>   (apply #'mapcar (cons #'list matrix)))

That doesn't look right. What do you gain by consing together the
function #'list and matrix?

It's quite simply:

  (apply #'mapcar #'list matrix)

The apply simply expands the last argument ((1 2) (3 4) (5 6)) into
individual arguments, and so then then mapcar takes the successive
corresponding elements from each of these lists in parallel and makes
lists out of them with #'list, thereby extracting rows out of columns.
From: Zach Beane
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <m31wmu9457.fsf@unnamed.xach.com>
"Kaz Kylheku" <········@gmail.com> writes:

> On Dec 19, 8:12 pm, "Pillsy" <·········@gmail.com> wrote:
>
> > (defun transpose (matrix)
> >   "Transposes a list of lists as if it were a matrix, so that ((1 2) (3
> > 4) (5 6)) => ((1 3 5) (2 4 6))"
> >   (apply #'mapcar (cons #'list matrix)))
> 
> That doesn't look right. What do you gain by consing together the
> function #'list and matrix?

I don't know about Pillsy, but when I was a novice I didn't grok the
"spreadable argument list designator" of apply and its LIST*-like
nature. I would go through all kinds of CONS and APPEND tricks to
produce full argument lists to pass to APPLY.

Zach
From: Tim Bradshaw
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166612957.350623.147410@i12g2000cwa.googlegroups.com>
Pillsy wrote:

> It's evidently very well-known, but it still kind of bent my head into
> a new shape when I first saw it.

Someone (Gabriel?) has it as an example of how people get seduced into
terrible performance problems in Lisp.  That was in the days when Lisp
was unusual in allowing such concise but awful code.  Now any Java
programmer can write

   String results = "";
   for (...) {
     results = results + " " + interestingThing;
   }

... and suddenly `java is slow'.

--tim
From: Pillsy
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166618246.442838.191930@79g2000cws.googlegroups.com>
Tim Bradshaw wrote:

> Pillsy wrote:

> > It's evidently very well-known, but it still kind of bent my head into
> > a new shape when I first saw it.

> Someone (Gabriel?) has it as an example of how people get seduced into
> terrible performance problems in Lisp.

Huh. It touches every element of the "matrix" once, just like any other
way I can think of that would do the same thing. Now using something
similar for matrix multiplication will kill your performance dead.

Maybe that's the seduction.... :^)

Cheers,
Pillsy
From: Kaz Kylheku
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166660572.632049.92530@48g2000cwx.googlegroups.com>
Pillsy wrote:
> Tim Bradshaw wrote:
>
> > Pillsy wrote:
>
> > > It's evidently very well-known, but it still kind of bent my head into
> > > a new shape when I first saw it.
>
> > Someone (Gabriel?) has it as an example of how people get seduced into
> > terrible performance problems in Lisp.
>
> Huh. It touches every element of the "matrix" once, just like any other
> way I can think of that would do the same thing. Now using something
> similar for matrix multiplication will kill your performance dead.

Or maybe not. You could do the transpose on one of the matrices, and
then just do dot-products with corresponding vectors. Something along
the lines of:

  (loop for row in left-matrix
            collecting (loop for column in (apply #'mapcar
                                                  #'list right-matrix))
                                 collecting (dot-product row column)))

having a suitably-defined DOT-PRODUCT.

Where's the terrible inefficiency? :)
From: Pillsy
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <1166664026.892123.159120@a3g2000cwd.googlegroups.com>
Kaz Kylheku wrote:
[...]
> Or maybe not. You could do the transpose on one of the matrices, and
> then just do dot-products with corresponding vectors. Something along
> the lines of:

>   (loop for row in left-matrix
>             collecting (loop for column in (apply #'mapcar
>                                                   #'list right-matrix))
>                                  collecting (dot-product row column)))
 
> having a suitably-defined DOT-PRODUCT.

Neat!
From: Rob Warnock
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <yfSdnTGTfcZ3vxTYnZ2dnUVZ_segnZ2d@speakeasy.net>
Zach Beane  <····@xach.com> wrote:
+---------------
| Have you seen any snippet of Lisp that made you do a double-take,
| work out what's happening, and then groan or smile?
...
|    (defun string-repeated (string count)
|      (format nil ···@{~A~:*}" count string))
+---------------

Hmmm...

    > (string-repeated "foo" 5)

    Error in format: No corresponding close brace.
      ··@{~A~:*}
	 ^
       [Condition of type FORMAT::FORMAT-ERROR]
    ...

But that small typo aside [it works when you add a "~" in front
of the closing brace], I've heard that it might run a *lot* faster
if you make one small modification:

    (defun string-repeated (string count)
      (let ((*print-pretty* nil))
	(format nil ···@{~A~:*~}" count string)))

Ouch! About 141 *times* faster, in compiled CMUCL!!
[4235 s versus 29.88 s, for 1000 repetitions of
(string-repeated (make-string 1000) 1000).]

That's very interesting, since my natural tendency would
have been to do this slightly differently, either this way
for clarity:

    (defun string-repeated (string count)
      (with-output-to-string (s)
	(dotimes (i count)
	  (write-sequence string s))))

[Runs at roughly the same speed as the second FORMAT version, and
conses the same, about 3 times the total of the result strings.]

Or this way for efficiency:

    (defun string-repeated (string count)
      (loop with result = (make-string (* (length string) count))
	    for i below (length result) by (length string)
	do (replace result string :start1 i)
	finally (return result)))

Hmmm... That's odd. Even though that one only conses 1/3 as much,
it runs 3.5 times *slower* than the second FORMAT version. Maybe
REPLACE is just badly coded? Perhaps if I unroll it myself:

    (defun string-repeated (string count)
      (declare (string string) (fixnum count) (optimize (speed 3)))
      (loop with slen of-type fixnum = (length string)
	    with rlen of-type fixnum = (* slen count)
	    with result of-type string = (make-string rlen)
	    for i of-type fixnum below rlen by slen
	do (loop for j of-type fixnum from i
		 and k of-type fixnum below slen
	     do (setf (aref result j) (aref string k)))
	finally (return result)))

A *little* Better. Like the REPLACE version, it only conses 1/3
as much as the second FORMAT version, but the latter and the
WRITE-SEQUENCE version are still 1.25 times faster than the
hand-coded one. Go figure.

Moral: Remember to bind *PRINT-PRETTY* to NIL when consing up
strings with FORMAT!


-Rob

p.s. GC time was ~3% on the slower versions and ~10% on the faster ones.

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Zach Beane
Subject: Re: tricks that made you grin or grimace
Date: 
Message-ID: <m3wt4m7km4.fsf@unnamed.xach.com>
····@rpw3.org (Rob Warnock) writes:

>     Error in format: No corresponding close brace.
>       ··@{~A~:*}
> 	 ^
>        [Condition of type FORMAT::FORMAT-ERROR]
>     ...

How embarrassing!

> That's very interesting, since my natural tendency would
> have been to do this slightly differently, either this way
> for clarity:
> 
>     (defun string-repeated (string count)
>       (with-output-to-string (s)
> 	(dotimes (i count)
> 	  (write-sequence string s))))

That's my tendency too. The initial context is this discussion on
reddit:

   http://programming.reddit.com/info/uf2g/comments/cuhck

I found

   (concatenate 'string
                (reduce (lambda (a s) (concatenate 'string a s))
                        (make-list 20 :initial-element "foo "))
                "fo"))

to be particularly unnatural and un-Common-Lispy. I started to write a
clear version, was struck by the concise and obscure potential of
FORMAT, and ended up with something like the initial format string in
this thread.

> Moral: Remember to bind *PRINT-PRETTY* to NIL when consing up
> strings with FORMAT!

Always a good call.

Zach