Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

lexical closure problem

51 views
Skip to first unread message

Jinsong Zhao

unread,
Sep 1, 2022, 10:06:46 AM9/1/22
to
Hi there,

Recently, I read Lisp 3rd Edition by Winston and Horn. In Chapter 15,
there is a example code:

(setf generator-with-dispatch-procedure
(let ((previous-power-of-two 1)
(reset-procedure
#'(lambda ()
(setf previous-power-of-two 1)))
(value-procedure
#'(lambda ()
(setf previous-power-of-two
(* previous-power-of-two 2)))))
;;The dispatch procedure:
#'(lambda (accessor)
(cond ((eq 'reset accessor) reset-procedure)
((eq 'value accessor) value-procedure)))))

I tried to invoke it like:
(funcall (funcall generator-with-dispatch-procedure 'value))

SBCL gives the error message:
The variable PREVIOUS-POWER-OF-TWO is unbound.

If I run
(funcall (funcall generator-with-dispatch-procedure 'reset))
before
(funcall (funcall generator-with-dispatch-procedure 'value))
I could get what I expect.

I could not understand why the error appears. Any hint?

Best,
Jinsong


Sam Steingold

unread,
Sep 1, 2022, 10:58:15 AM9/1/22
to
> * Jinsong Zhao <wfm...@lrnu.arg> [2022-09-01 22:06:39 +0800]:
>
> (setf generator-with-dispatch-procedure
> (let ((previous-power-of-two 1)

replace `let' with `let*'

> (reset-procedure
> #'(lambda ()
> (setf previous-power-of-two 1)))
> (value-procedure
> #'(lambda ()
> (setf previous-power-of-two
> (* previous-power-of-two 2)))))
> ;;The dispatch procedure:
> #'(lambda (accessor)
> (cond ((eq 'reset accessor) reset-procedure)
> ((eq 'value accessor) value-procedure)))))
>
> I tried to invoke it like:
> (funcall (funcall generator-with-dispatch-procedure 'value))
>
> SBCL gives the error message:
> The variable PREVIOUS-POWER-OF-TWO is unbound.

Actually, SBCL tells you that this will happen as soon as you evaluate
the above `setf' form:

--8<---------------cut here---------------start------------->8---
; in: SETF GENERATOR-WITH-DISPATCH-PROCEDURE
; (PREVIOUS-POWER-OF-TWO 1)
;
; caught STYLE-WARNING:
; The variable PREVIOUS-POWER-OF-TWO is defined but never used.
; in: SETF GENERATOR-WITH-DISPATCH-PROCEDURE
; (* PREVIOUS-POWER-OF-TWO 2)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::PREVIOUS-POWER-OF-TWO
;
; compilation unit finished
; Undefined variable:
; PREVIOUS-POWER-OF-TWO
; caught 1 WARNING condition
--8<---------------cut here---------------end--------------->8---

this tells you that `previous-power-of-two' is not known to
`reset-procedure' and `value-procedure' and it is not used.

--
Sam Steingold (https://aphar.dreamwidth.org/) on darwin Ns 10.3.2113
https://lastingimpactpsychology.com https://steingoldpsychology.com
https://jihadwatch.org https://fairforall.org https://ij.org/ https://ffii.org
Don't hit a man when he's down -- kick him; it's easier.

Ben Bacarisse

unread,
Sep 1, 2022, 11:49:32 AM9/1/22
to
Jinsong Zhao <jsz...@yeah.net> writes:

> Recently, I read Lisp 3rd Edition by Winston and Horn. In Chapter 15,
> there is a example code:
>
> (setf generator-with-dispatch-procedure
> (let ((previous-power-of-two 1)
> (reset-procedure
> #'(lambda ()
> (setf previous-power-of-two 1)))
> (value-procedure
> #'(lambda ()
> (setf previous-power-of-two
> (* previous-power-of-two 2)))))
> ;;The dispatch procedure:
> #'(lambda (accessor)
> (cond ((eq 'reset accessor) reset-procedure)
> ((eq 'value accessor) value-procedure)))))
>
> I tried to invoke it like:
> (funcall (funcall generator-with-dispatch-procedure 'value))
>
> SBCL gives the error message:
> The variable PREVIOUS-POWER-OF-TWO is unbound.

Indeed. I find the code odd. What is it intended to illustrate? To
work as it apparently should you need let* there.

> If I run
> (funcall (funcall generator-with-dispatch-procedure 'reset))
> before
> (funcall (funcall generator-with-dispatch-procedure 'value))
> I could get what I expect.

Well, except for the fact that a global variable called
previous-power-of-two comes into existence! That is not intended, I
think.

> I could not understand why the error appears. Any hint?

The two anonymous functions in the let can't see the binding for
previous-power-of-two.

--
Ben.

Spiros Bousbouras

unread,
Sep 1, 2022, 12:27:28 PM9/1/22
to
Because reset-procedure and value-procedure are not within the scope of
previous-power-of-two ; or , to use the terminology of the book , within the
"gatekeeper's list" created for previous-power-of-two .Compare with an
earlier example :

(setf g1
(let ((previous-power-of-two 1))
#'(lambda () ..... )))

This works because the lambda is within the scope for previous-power-of-two
created by LET .I suspect that the authors made a mistake : they intended to
use LET* for the generator-with-dispatch-procedure example but they forgot
and in their testing they always happened to do first
(funcall (funcall generator-with-dispatch-procedure 'reset))
hence they never noticed the mistake. The way they have written the code ,
the first time you do
(funcall (funcall generator-with-dispatch-procedure 'reset))

a special variable previous-power-of-two gets created. Note that then
you can just type on the REPL
previous-power-of-two

and it shows you its current value which violates what the book says on page
217 : "However some programmers dislike having the state variable exposed. In
principle other procedures could alter it inadvertently." and the examples
which follow {including the one your question is about} are meant to show you
how you can avoid this.

With the definition in the book , I have the following interaction with SBCL
{omitting warnings}

* (funcall (funcall generator-with-dispatch-procedure 'reset))
1

* previous-power-of-two
1

* (funcall (funcall generator-with-dispatch-procedure 'value))
2

* (setf previous-power-of-two 7)
7

* (funcall (funcall generator-with-dispatch-procedure 'value))
14

Now if I terminate and restart SBCL and replace the let in the code of
the book by let* , I get the following :

* (funcall (funcall generator-with-dispatch-procedure 'value))
2

{ Note that there is no error now. }

* previous-power-of-two

debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "initial thread" RUNNING {A8346F9}>:
The variable PREVIOUS-POWER-OF-TWO is unbound.

* (setf previous-power-of-two 7)
7

* (funcall (funcall generator-with-dispatch-procedure 'value))
4

* (funcall (funcall generator-with-dispatch-procedure 'reset))
1

* (funcall (funcall generator-with-dispatch-procedure 'value))
2

* previous-power-of-two
7

When you do (funcall generator-with-dispatch-procedure ...) , the 2
functions access the lexical previous-power-of-two which is private to
the 2 functions and has a separate value from the special
previous-power-of-two which was created when I did
(setf previous-power-of-two 7)

.I believe this is what the book was aiming to exhibit.

--
vlaho.ninja/prog

steve g

unread,
Oct 24, 2022, 5:47:49 PM10/24/22
to
It seems to work for me I think..?

CL-USER> (funcall (funcall generator-with-dispatch-procedure 'value))
2
CL-USER> (funcall (funcall generator-with-dispatch-procedure 'reset))
1
CL-USER>


Try using a let to bind the value outside the `setf'...

(let ((previous-power-of-two 1)
(setq generator-with-dispatch-procedure

(reset-procedure
#'(lambda ()
(setf previous-power-of-two 1)))
(value-procedure
#'(lambda ()
(setf previous-power-of-two
(* previous-power-of-two 2)))))
;;The dispatch procedure:
#'(lambda (accessor)
(cond ((eq 'reset accessor) reset-procedure)
((eq 'value accessor) value-procedure)))))


The use of setq is stylistic; this might help...



Kaz Kylheku

unread,
Oct 25, 2022, 1:19:07 PM10/25/22
to
On 2022-10-24, steve g <sgoned...@gmail.com> wrote:
> Jinsong Zhao wrote:
>> I could not understand why the error appears. Any hint?
>
> It seems to work for me I think..?

Three others had to change let to let*, so that is amazing.

Do you have a customized environment in which *let* is sequential
by default, or do you just filter such problems in your
fingers without being consciously aware of it any more?

--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal

steve

unread,
Oct 25, 2022, 2:17:37 PM10/25/22
to
Kaz Kylheku <864-11...@kylheku.com> writes:

> On 2022-10-24, steve g <sgoned...@gmail.com> wrote:
>> Jinsong Zhao wrote:
>>> I could not understand why the error appears. Any hint?
>>
>> It seems to work for me I think..?
>
> Three others had to change let to let*, so that is amazing.

an old thread.

> Do you have a customized environment in which *let* is sequential
> by default, or do you just filter such problems in your
> fingers without being consciously aware of it any more?

I dunno what you mean. could you explain filtering?



steve

unread,
Oct 25, 2022, 2:28:45 PM10/25/22
to
Kaz Kylheku <864-11...@kylheku.com> writes:

> On 2022-10-24, steve g <sgoned...@gmail.com> wrote:
>> Jinsong Zhao wrote:
>>> I could not understand why the error appears. Any hint?
>>
>> It seems to work for me I think..?
>
> Three others had to change let to let*, so that is amazing.
>
> Do you have a customized environment in which *let* is sequential


I just ment to bring the binding outside of the setf; that is why i use
setq.

i just typed that in; it looks like it worked...

(setq generator-with-dispatch-procedure
(let ((previous-power-of-two 1)
(reset-procedure
#'(lambda ()
(setf previous-power-of-two 1)))
(value-procedure
#'(lambda ()
(setf previous-power-of-two
(* previous-power-of-two 2)))))
;;The dispatch procedure:
#'(lambda (accessor)
(cond ((eq 'reset accessor) reset-procedure)
((eq 'value accessor) value-procedure)))))


> by default, or do you just filter such problems in your
> fingers without being consciously aware of it any more?

i usually use more humility.
0 new messages