Subject: Re: Closures vs. objects
From: rpw3@rpw3.org (Rob Warnock)
Date: Wed, 05 Mar 2003 03:22:27 -0600
Newsgroups: comp.lang.lisp
Message-ID: <18OcncT0S79OXPijXTWc-g@speakeasy.net>
Tj <tj_scarlet@yahoo.com> wrote:
+---------------
| Lexical closures are often mentioned as an important advantage. 
| However, languages which don't have them often use things like objects
| for these tasks.  I understand closures are very elegant and I
| personally like them, but am I missing something in thinking that
| this is a somewhat flimsy issue?
+---------------

Yes, I think so. While not strictly "necessary", they allow you to
*significantly* extend the protocols of higher-order functions without
requiring any changes to them, nor any knowledge of your extensions.

+---------------
| Maybe my sense is that other issues are more important. Like, map is
| overwhelmingly painful in some languages.
+---------------

Well consider this: In Lisp you can pass a *closure* to map, too!
Or to any other higher-order function.

As a completely trivial example, consider using that ability to instrument
your particular Lisp implementation's built-in SORT function to see how
often and in what order it compares things while it's sorting, even though
there are no such hooks defined as part of SORT's calling sequence:

	> (let ((seq (copy-seq '(8 3 1 9 2 0 4 6 5 7)))
		(trace '())
		(count 0))
	    (flet ((my-less-than (x y)        ; closure over TRACE & COUNT
		     (push (cons x y) trace)
		     (incf count)
		     (< x y)))
	      (setq seq (sort seq #'my-less-than))
	      (values seq (nreverse trace) count)))
	=>
	(0 1 2 3 4 5 6 7 8 9)
	((3 . 8) (9 . 1) (0 . 2) (6 . 4) (7 . 5) (1 . 3)
	 (9 . 3) (9 . 8) (4 . 0) (4 . 2) (0 . 1) (2 . 1)
	 (2 . 3) (4 . 3) (4 . 8) (6 . 8) (5 . 0) (5 . 1)
	 (5 . 2) (5 . 3) (5 . 4) (5 . 6) (7 . 6) (7 . 8))
	24
	> 

So SORT first compared 3 & 8, then it compared 9 & 1, then 0 & 2, etc.,
making a total of 24 comparisons in all. Interesting.

In C, you can fake closures, but *only* by designing the protocol for
*all* of your higher-order functions to accept -- for each function
pointer you might pass it as an argument -- *both* a function pointer
and an "opaque data cookie" [i.e., a place to hide the closure data],
the latter of which *must* be passed to the corresponding function
whenever it's called (in addition to the other parameters it requires).

But the last time I looked at the man pages for libc's qsort(3),
heapsort(3), & mergesort(3), the comparison function argument was
only defined to have two parameters, e.g.:

	void qsort(void *base, size_t nmemb, size_t size,
                   int (*compar)(const void *, const void *));

Common Lisp SORT also only knows about the two required parameters for
the binary comparison predicate (*not* anything about any additional
cookie to pass it), yet even so we were able to access the lexical
environment of the calling code, without changing the interface protocol.


-Rob

p.s. Another problem with faking closures in C is that the "closure
data" often has to be allocated with "malloc()" before the call to
the higher-order function, and then deallocated after the call returns.
Sometimes. Depending of the function being passed in this particular case
(and the closure data being passed in this particular case). But if you
get it wrong... Can you say "memory leak"? Or maybe "double-free panic"?

Or consider how much simpler GUI (and other) "callbacks" become if you
always pass them fully-general closures rather "with a cookie here, no
cookie there, sometimes a cookie, sometimes no cookie... Old X Windows
had a crash, eee-aii-eeee-aaiii-OOHHHHH!"

-----
Rob Warnock, PP-ASEL-IA		<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607