knowledge-database (beta)

Current group: comp.lang.lisp

FLET/LABELS question

FLET/LABELS question  
David Sletten
 Re: FLET/LABELS question  
Harald Hanche-Olsen
 Re: FLET/LABELS question  
David Sletten
 Re: FLET/LABELS question  
David Steuber
 Re: FLET/LABELS question  
David Sletten
 Re: FLET/LABELS question  
Harald Hanche-Olsen
From:David Sletten
Subject:FLET/LABELS question
Date:Sun, 23 Jan 2005 09:00:07 GMT
The special operators LABELS and FLET can be used to define local
functions. However, under certain circumstances they appear to do their
work at compile time rather than runtime. By contrast, other special
operators must always do work at runtime:
(if (> x 5) 'foo 'bar)
Here IF must respond conditionally to the value of X. But the following
examples seem to suggest that LABELS doesn't always define a function at
runtime.

Consider a tail-recursive Fibonacci sequence definition:
(defun fibonacci-tr-1 (n)
(fibonacci-tr-aux n 1 0))

(defun fibonacci-tr-aux (n f1 f2)
(if (zerop n)
f1
(fibonacci-tr-aux (1- n) (+ f1 f2) f1)))

The auxiliary function does all of the main work but shouldn't
necessarily be exposed this way. Instead we can tuck it away inside the
main function:
(defun fibonacci-tr-2 (n)
(labels ((fibonacci-tr-aux (n f1 f2)
(if (zerop n)
f1
(fibonacci-tr-aux (1- n) (+ f1 f2) f1))))
(fibonacci-tr-aux n 1 0)))

Based on my understanding of LABELS, however, I thought that this would
redefine the auxiliary function each time FIBONACCI-TR-2 was called. The
alternative seemed to be this awkward code:
(labels ((fibonacci-tr-aux (n f1 f2)
(if (zerop n)
f1
(fibonacci-tr-aux (1- n) (+ f1 f2) f1))))
(defun fibonacci-tr-2a (n)
(fibonacci-tr-aux n 1 0)))

This seems less than ideal since the DEFUN is harder to find.

When I timed these functions I expected TR-1 or TR-2A to be fastest
since they did less work by not having to define the auxiliary function
each time. However, in both SBCL and CLISP TR-2 and TR-2A perform about
the same. In fact, with CLISP, TR-1 is substantially slower than either
of the other two. It is evident that the LABELS in TR-2 is _not_
creating a new function at runtime, but rather freezes the definition
when the enclosing function is compiled.

There is a further case which seemed to me even more obvious that LABELS
would have to redefine a function at runtime. This is the situation
where the LABELS function creates a closure over a local variable of the
enclosing function:
(defun fibonacci-tr-3 (n)
(labels ((fibonacci-tr-aux (i f1 f2)
(if (= i n)
f1
(fibonacci-tr-aux (1+ i) (+ f1 f2) f1))))
(fibonacci-tr-aux 0 1 0)))

Here the parameter N could change on each invocation of FIBONACCI-TR-3,
so it seemed that LABELS couldn't generate a fixed auxiliary function.
However, this function is just as fast as the others. Perhaps the
compiler is smart enough to ensure references to N in the LABELS
function simply point to whatever storage has been allocated for the
outer function?

I guess the issue is more general than just LABELS/FLET. The following
seemed to be the same problem:
(defun add-to-elts (n l)
(mapcar #'(lambda (elt)
(+ n elt))
l))

The LAMBDA function creates a closure over ADD-TO-ELTS' parameter N.
Rather than creating a new LAMBDA function each time ADD-TO-ELTS is
called perhaps the compiler arranges things as above with FIBONACCI-TR-3.

Finally, there are cases where LABELS or LAMBDA _must_ generate
functions at runtime:
(defun counter-maker (n)
#'(lambda ()
(incf n)))

Here, two separate invocations of COUNTER-MAKER generate functions which
are never EQ.

Have I got things straightened out here? In particular, is this behavior
implementation-specific? Or is it so widespread as to be expected? Is
defensive coding such as TR-2A a good idea?

Thanks,
David Sletten
From:Harald Hanche-Olsen
Subject:Re: FLET/LABELS question
Date:23 Jan 2005 13:13:03 +0100
+ David Sletten :

| The special operators LABELS and FLET can be used to define local
| functions. However, under certain circumstances they appear to do
| their work at compile time rather than runtime.

Sure. They are compile-time concepts, after all, no? There is no
need for the compiler to create anything recognizable as a function,
unless of course the user chooses to expose the function using
(function foo). Otherwise, all that is needed is a bit of machine
code implementing. The knowledge on how to call it can be local to
the LABELS form.

| (defun fibonacci-tr-2 (n)
| (labels ((fibonacci-tr-aux (n f1 f2)
| (if (zerop n)
| f1
| (fibonacci-tr-aux (1- n) (+ f1 f2) f1))))
| (fibonacci-tr-aux n 1 0)))
|
| Based on my understanding of LABELS, however, I thought that this
| would redefine the auxiliary function each time FIBONACCI-TR-2 was
| called.

I think you are thinking about this in a much too literal form. What
it really means is that the form (fibonacci-tr-aux n 1 0) compiles
into machine code to call the machine code that resulted from
compiling the body of fibonacci-tr-aux. In the case of interpreted
code, it might be different - but I believe that even then, the
interpreter may choose to do the necessary optimization while
processing the definition of fibonacci-tr-2.

| When I timed these functions I expected TR-1 or TR-2A to be fastest
| since they did less work by not having to define the auxiliary
| function each time. However, in both SBCL and CLISP TR-2 and TR-2A
| perform about the same.

Why do you care about what timing tells you? (Apart from the ovious
goal of writing optimal code, of course.) The only relevant question
is whether the different implementation choices would result in
different values being computed.

| (defun fibonacci-tr-3 (n)
| (labels ((fibonacci-tr-aux (i f1 f2)
| (if (= i n)
| f1
| (fibonacci-tr-aux (1+ i) (+ f1 f2) f1))))
| (fibonacci-tr-aux 0 1 0)))
|
| Here the parameter N could change on each invocation of
| FIBONACCI-TR-3, so it seemed that LABELS couldn't generate a fixed
| auxiliary function. However, this function is just as fast as the
| others. Perhaps the compiler is smart enough to ensure references to
| N in the LABELS function simply point to whatever storage has been
| allocated for the outer function?

Precisely. The compiler is /allowed/ to be smart, so long as it
produces correct code.

| Finally, there are cases where LABELS or LAMBDA _must_ generate
| functions at runtime:
| (defun counter-maker (n)
| #'(lambda ()
| (incf n)))
|
| Here, two separate invocations of COUNTER-MAKER generate functions
| which are never EQ.

But even here, the body of the lambda will typically be compiled at
compile-time. All that remains at runtime is to create a closure,
which will involve creating a small data structure with space for the
variable n together with a link to the compiled code.

| Have I got things straightened out here? In particular, is this
| behavior implementation-specific?

No, and yes. Implementations are free to do whatever they please, so
long as they produce correct code. The point is that you should not
be able to detect any difference, other than through timing or playing
with compiled-function-p.

| Is defensive coding such as TR-2A a good idea?

I don't see why it should be.

--
* Harald Hanche-Olsen
- Debating gives most of us much more psychological satisfaction
than thinking does: but it deprives us of whatever chance there is
of getting closer to the truth. -- C.P. Snow
From:David Sletten
Subject:Re: FLET/LABELS question
Date:Sun, 23 Jan 2005 22:20:32 GMT
Harald Hanche-Olsen wrote:


>
> I think you are thinking about this in a much too literal form. What
> it really means is that the form (fibonacci-tr-aux n 1 0) compiles
> into machine code to call the machine code that resulted from
> compiling the body of fibonacci-tr-aux. In the case of interpreted
> code, it might be different - but I believe that even then, the
> interpreter may choose to do the necessary optimization while
> processing the definition of fibonacci-tr-2.
>
> | When I timed these functions I expected TR-1 or TR-2A to be fastest
> | since they did less work by not having to define the auxiliary
> | function each time. However, in both SBCL and CLISP TR-2 and TR-2A
> | perform about the same.
>
> Why do you care about what timing tells you? (Apart from the ovious
> goal of writing optimal code, of course.) The only relevant question
> is whether the different implementation choices would result in
> different values being computed.
>

Thanks for your reply Harald. The reason I cared about timing is
precisely because it appears to have disproved my hypothesis. LABELS is
_not_ doing extra work at runtime. Consequently, the awkward style of
TR-2A is unnecessary. (Good news--I'm not used to Lisp making me bend
over backwards.) How else would I as a programmer, rather than an
implementer, gain any insight beyond my 'too literal' understanding? Is
there a section in the standard that clarifies this?

Based on my timing results and your comments I would now conclude that
TR-2 might be faster than TR-1 (rather than slower as I expected)
because the compiler can maybe inline the auxiliary function. TR-1 has
to rely on an explicit call to a second function.

> | Finally, there are cases where LABELS or LAMBDA _must_ generate
> | functions at runtime:
> | (defun counter-maker (n)
> | #'(lambda ()
> | (incf n)))
> |
> | Here, two separate invocations of COUNTER-MAKER generate functions
> | which are never EQ.
>
> But even here, the body of the lambda will typically be compiled at
> compile-time. All that remains at runtime is to create a closure,
> which will involve creating a small data structure with space for the
> variable n together with a link to the compiled code.
>

This was especially counterintuitive, but your explanation makes perfect
sense. The compiler just creates a generic function whose details are
plugged in with a specific environment at runtime.

Thanks,
David Sletten
From:David Steuber
Subject:Re: FLET/LABELS question
Date:24 Jan 2005 01:11:21 -0500
David Sletten writes:

> Thanks for your reply Harald. The reason I cared about timing is
> precisely because it appears to have disproved my hypothesis. LABELS
> is _not_ doing extra work at runtime. Consequently, the awkward style
> of TR-2A is unnecessary. (Good news--I'm not used to Lisp making me
> bend over backwards.) How else would I as a programmer, rather than an
> implementer, gain any insight beyond my 'too literal' understanding?
> Is there a section in the standard that clarifies this?

You should take another look at:

http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm

Also, you might find DISASEMBLE to be of interest:

CL-USER> (defun factorial (n)
(labels ((f (x)
(if (< x 2)
x
(* x (f (1- x))))))
(f n)))
FACTORIAL
CL-USER> (factorial 6)
720
CL-USER> (disassemble 'factorial)
(TWNEI NARGS 4)
(MFLR LOC-PC)
(BLA .SPSAVECONTEXTVSP)
(VPUSH ARG_Z)
(LWZ ARG_Z 0 VSP)
(SET-NARGS 1)
(LWZ TEMP2 '# FN)
(BLA .SPRESTORECONTEXT)
(MTLR LOC-PC)
(LWZ TEMP0 -2 TEMP2)
(MTCTR TEMP0)
(BCTR)
; No value

Rather than trying to use timings to see what the code does, have a
look at the generated code to see what the code does.

CL-USER> (defun factorial (n)
(if (< n 2)
n
(* n (factorial (1- n)))))
FACTORIAL
CL-USER> (factorial 6)
720
CL-USER> (disassemble 'factorial)
L0
(TWNEI NARGS 4)
(MFLR LOC-PC)
(BLA .SPSAVECONTEXTVSP)
(:REGSAVE SAVE0 0)
(VPUSH SAVE0)
(MR SAVE0 ARG_Z)
(MR ARG_Y SAVE0)
(LI ARG_Z '2)
(BLA .SPBUILTIN-LT)
(CMPWI ARG_Z NIL)
(BEQ L52)
(MR ARG_Z SAVE0)
(LWZ SAVE0 0 VSP)
(BA .SPPOPJ)
L52
(MR ARG_Y SAVE0)
(LI ARG_Z '1)
(BLA .SPBUILTIN-MINUS)
(SET-NARGS 1)
(MR TEMP2 FN)
(BL L0)
(MR ARG_Y SAVE0)
(LWZ SAVE0 0 VSP)
(BLA .SPRESTORECONTEXT)
(MTLR LOC-PC)
(BA .SPBUILTIN-TIMES)

; No value
CL-USER> (defun factorial (n)
(do ((f n (* f n)))
((< n 2) (max f 1))
(decf n)))
FACTORIAL
CL-USER> (factorial 6)
720
CL-USER> (disassemble 'factorial)
(TWNEI NARGS 4)
(MFLR LOC-PC)
(BLA .SPSAVECONTEXTVSP)
(:REGSAVE SAVE1 0)
(VPUSH SAVE0)
(VPUSH SAVE1)
(MR SAVE0 ARG_Z)
(MR SAVE1 SAVE0)
(B L64)
L32
(MR ARG_Y SAVE0)
(LI ARG_Z '1)
(BLA .SPBUILTIN-MINUS)
(MR SAVE0 ARG_Z)
(MR ARG_Y SAVE1)
(MR ARG_Z SAVE0)
(BLA .SPBUILTIN-TIMES)
(MR SAVE1 ARG_Z)
L64
(MR ARG_Y SAVE0)
(LI ARG_Z '2)
(BLA .SPBUILTIN-LT)
(CMPWI ARG_Z NIL)
(BEQ L32)
(MR ARG_Y SAVE1)
(LI ARG_Z '1)
(BLA .SPBUILTIN-GT)
(CMPWI ARG_Z NIL)
(BEQ L112)
(MR ARG_Z SAVE1)
(B L116)
L112
(LI ARG_Z '1)
L116
(LWZ SAVE1 0 VSP)
(LWZ SAVE0 4 VSP)
(BA .SPPOPJ)

; No value
CL-USER>

--
An ideal world is left as an excercise to the reader.
--- Paul Graham, On Lisp 8.1
From:David Sletten
Subject:Re: FLET/LABELS question
Date:Mon, 24 Jan 2005 07:49:29 GMT
David Steuber wrote:


>
> You should take another look at:
>
> http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm
>

What is it that I am missing which is clarified on that page? I've read
it many times but don't see anything relevant to my question.

> Also, you might find DISASEMBLE to be of interest:
>

Yes, clearly this is useful. However, it is also obviously _completely_
implementation specific. I was curious about whether I can rely on this
behavior (LABELS finished at compile time) in any Common Lisp system.

Thanks anyway...

David Sletten
From:Harald Hanche-Olsen
Subject:Re: FLET/LABELS question
Date:23 Jan 2005 13:24:23 +0100
+ Harald Hanche-Olsen :

| + David Sletten :
|
| | The special operators LABELS and FLET can be used to define local
| | functions. However, under certain circumstances they appear to do
| | their work at compile time rather than runtime.
|
| Sure. They are compile-time concepts, after all, no? There is no
| need for the compiler to create anything recognizable as a function,

except to the debugger of course. But nothing stops the debugger from
having access to all sorts of extra information about which piece of
machine code corresponds to what.

| unless of course the user chooses to expose the function using
| (function foo). Otherwise, all that is needed is a bit of machine
| code implementing

foo. (forgot a word, but I hope the meaning was clear).

--
* Harald Hanche-Olsen
- Debating gives most of us much more psychological satisfaction
than thinking does: but it deprives us of whatever chance there is
of getting closer to the truth. -- C.P. Snow
   

Copyright © 2006 knowledge-database   -   All rights reserved