Message from discussion
Implementation Favoritism, a question of Lisp mindsets
Path: archiver1.google.com!news1.google.com!newsfeed.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!news-xfer.cox.net!cox.net!newspeer1-gui.server.ntli.net!ntli.net!newsfep1-win.server.ntli.net.POSTED!53ab2750!not-for-mail
Newsgroups: comp.lang.lisp
From: Gareth McCaughan <Gareth.McCaug...@pobox.com>
Subject: Re: Implementation Favoritism, a question of Lisp mindsets
References: <jkCma.9$NE4.382@paloalto-snr1.gtei.net> <20030414135114.24021.00000821@mb-ch.aol.com> <b7gl6c$sn9$07$1@news.t-online.com> <20030415104420.G13181@lain.cheme.cmu.edu> <b7hrhm$o6b$07$1@news.t-online.com> <20030415182228.H13181@lain.cheme.cmu.edu> <160420030037089214%nospam@iam.invalid> <878yua4p14.fsf@bird.agharta.de> <160420031526227070%nospam@iam.invalid> <871xzr5rmu.fsf@tenkan.org> <240420032331017657%nospam@iam.invalid> <u1cmel15.fsf@ccs.neu.edu> <260420030346386552%nospam@iam.invalid> <bryqadzs.fsf@ccs.neu.edu> <280420032247144579%nospam@iam.invalid> <m3r87loheb.fsf@javamonkey.com> <010520030523544492%nospam@iam.invalid> <ey3vfwuepjf.fsf@cley.com> <010520031630305344%nospam@iam.invalid> <86fznyurn4.fsf@bogomips.optonline.net> <020520030140499402%nospam@iam.invalid> <861xzh4oew.fsf@bogomips.optonline.net> <020520031429570028%nospam@iam.invalid>
Reply-To: Gareth.McCaug...@pobox.com
Organization: International Pedant Conspiracy
Message-ID: <slrnbb5ses.on.Gareth.McCaughan@g.local>
User-Agent: slrn/0.9.7.4 (FreeBSD)
Lines: 177
Date: Fri, 2 May 2003 23:31:56 +0100
NNTP-Posting-Host: 62.253.132.234
X-Complaints-To: abuse@ntlworld.com
X-Trace: newsfep1-win.server.ntli.net 1051915600 62.253.132.234 (Fri, 02 May 2003 23:46:40 BST)
NNTP-Posting-Date: Fri, 02 May 2003 23:46:40 BST
Mark Conrad wrote:
[someone else:]
> (let ((x 1))
> (defun x () x)
> (defun set-x (new)
> (setf x new)))
> *********************************************
>
>
> Running his code in MCL ver' 4.3
> ? (x)
> 1
> ? (set-x 3)
> 3
> ? (x)
> 3
> ?
>
> At the very least, this is unexpected behavior. It is yet another
> exception to the general behavior of CL that has to be memorized and
> allowed for so it won't bite me later.
How so? It's *exactly* the expected behaviour.
This is precisely what closures are about. What
general behaviour do you think it's an exception to?
What is it about this behaviour that makes you
call it "nasty" later on?
> Now let's make small changes in my above code to demonstrate the
> unexpected nasty behavior, nasty behavior that is *not* explained in
> any of the books that have been recommended to me in this NG.
>
> (defvar a)
> (defvar b)
>
> (let ((x 'value-of-lexical-x) (y 'value-of-lexical-y))
> (setq a #'(lambda () x))
> (setq b #'(lambda () y))
> (setq x 'screwed-up-value-of-lexical-x)
> (setq y 'screwed-up-value-of-lexical-y)
> (values))
>
> ? (funcall a)
> SCREWED-UP-VALUE-OF-LEXICAL-X
>
> ? (funcall b)
> SCREWED-UP-VALUE-OF-LEXICAL-Y
Why's it nasty? I'd ask "why's it unexplained?", but
that's not really the right question. The right question
is: What is it you think you know that makes it
*require* explanation?
I'm not asking just out of curiosity, by the way.
I'd like to explain, but I don't see how to explain
until I know what it is that needs explaining. Maybe
this potted summary will help in the interim:
It's common to say that (1) a closure is a function
plus its environment, and (2) an environment is a
mapping from names to values. In fact, I've said
both those things several times myself. But #2 may
be misleading, because an environment is a *changeable*
mapping from names to values. It might be better to
call it a mapping from names to *value-holders*, or,
more simply, to variables. So, when you say
(let ((x 'value-of-lexical-x) (y 'value-of-lexical-y))
...)
you have created new variables X and Y. They're visible
only within the scope of the LET (unless X or Y has been
declared special, of course). But they're real variables,
and they continue to exist after the LET form has finished
being processed.
Now, the things you've put into A and B inside the LET
form are closures: each is a function plus (a reference
to) its environment. That environment consists of the
bindings of X and Y. Note: the *bindings*, not the
*values*. If you change what X and Y are bound to,
that change is visible to the closures in A and B.
Which is exactly what happens when you do the third
and fourth SETQs. The closures in A and B still refer
to the same variables X and Y, but now the values of
those variables have changed.
You obviously find this nasty, but I don't know why.
Here's an example of somewhere where it might be
useful. The MAPHASH function calls a user-supplied
function for each (key, value) pair in a hash table.
So you can do things like this:
(let ((boring-keys nil))
(maphash (lambda (k v)
(when (null v)
(push k boring-keys)))
*my-hash-table*)
(format t "~&Boring keys:~{ ~A~^,~}." boring-keys)
(do-something-else-with boring-keys))
Notice that what you're passing into MAPHASH is a closure
that needs to be able to alter BORING-KEYS.
As it happens, you can do that without closures with
either LOOP or WITH-HASH-TABLE-ITERATOR. So here's another
example. Suppose you're doing some kind of mathematical
computing; numerical integration, perhaps. You have a
bunch of algorithms you've been developing for adaptive
quadrature (i.e., calculating integrals to a prescribed
level of accuracy, working out what function evaluations
they need to do to achieve that accuracy) and you want
to see how they compare. Then:
- You can count the number of times one of the algorithms
evaluates the function, like this:
(defun count-evaluations (integrator integrand)
(let ((n 0))
(funcall integrator (lambda (x) (incf n) (funcall integrand x))
*lower-bound* *upper-bound*)
n))
- You can get a list of the arguments at which it evaluates
the function, like this:
(defun evaluation-points (integrator integrand)
(let ((result '()))
(funcall integrator (lambda (x)
(push x result)
(funcall integrand x))
*lower-bound* *upper-bound*)
result))
- You can count the number of times it does two successive
evaluations very close together, like this:
(defun count-near-repetitions (integrator integrand)
(let ((n 0)
(last nil))
(funcall integrator (lambda (x)
(when (and last (<= (abs (- x last)) *delta*))
(incf n))
(setq last x)
(f x))
*lower-bound* *upper-bound*)
n))
All this without having to make any change to the code
of the integrators or the integrand functions. And this
*requires* the ability to alter variables you've closed
over. You call that "nasty", but it isn't nasty; it's
at least half of the point.
> I don't want to "piss people off" so this will likely be my last post
> in this NG.
If you want to answer my questions above by e-mail,
feel free. But I doubt answering them in c.l.l will
piss anyone off very much.
> I can see now why there are so few new newbies adopting CL as their
> preferred language, when it is apparent how cantankerous and downright
> ornery people are in this NG.
I think you'd find much less cantankerousness if you
didn't assume that whenever CL doesn't behave the way
you expect it to that indicates something broken with
either CL or the books you're reading. You'd learn
faster, too :-).
--
Gareth McCaughan Gareth.McCaug...@pobox.com
.sig under construc