From: Kaz Kylheku on
On 2009-11-03, Captain Obvious <udodenko(a)users.sourceforge.net> wrote:
> P> What's the advantage of using the MOP over a straight-up closure in
> P> this instance?
>
> Might have better performance. If you use &rest and then apply
> I guess most compilers will make a list, that is, you have more consing.
>
> (lambda (&rest args)
> (let ((fn ....))
> (apply fn args)))

It's a poor Lisp compiler which conses a list for code which only passes the
&rest argument down to another function with apply.

Pascal's code went right over your head there. The advantage of the MOP
solution has nothing to do with this. And it has &rest/apply processing buried
in it.

What Pascal's code does is allow the user's function to be called several times
without being compiled. It counts how many calls there are. When the calls
exceed a threshold, then the object compiles the function.

When the function is compiled, the object is able to replace its own closure
with that compiled function. So subsequent calls go directly to that function,
bypassing the threshold check.

You can't do that with a lambda closure, because a lambda closure cannot
replace future calls to itself with a different closure.
From: Kaz Kylheku on
On 2009-11-03, Pascal Costanza <pc(a)p-cos.net> wrote:
> If you want your code to be very sophisticated, you can actually also
> implement your own version that uses interpreted code by default, counts
> how often a particular runtime-generated function is called, and after a
> certain threshold, compiles into a more efficient version (under the
> assumption that it will continue to be called even more often).
>
> Here is a sketch [untested]:
>
> (defclass smart-function (funcallable-standard-object) ()
> (:metaclass funcallable-standard-class))
>
> (defparameter *threshold* 10)
>
> (defun make-smart-function (body)
> (let ((fun (make-instance 'smart-function)))
> (set-funcallable-instance-function
> fun
> (let ((counter 0) (funfun (eval body)))
> (lambda (&rest args)
> (declare (dynamic-extent args)
> (when (> (incf counter) *threshold*)
> (unless (compiled-function-p funfun)
> (setq funfun (compile nil body)))
> (set-funcallable-instance-function fun funfun))
> (apply funfun args))))
> fun))
>
> (defmacro smart-lambda ((&rest args) &body body)
> `(make-smart-function `(lambda ,args ,@body)))
>
>
> Yes, this requires the CLOS MOP. ;)

Uh, you can do this with the existing standard funcallable objects. Namely,
instances of this highly curious SYMBOL class, which have a standard
slot given by the accessor SYMBOL-FUNCTION. :)

;; tested modifications to untested code
;; fixed unbalanced parentheses in declare
;; extra levels of unquote added to backquote
;; class replaced by symbol

(defparameter *threshold* 10)

(defun make-smart-function (body)
(let ((fun (gensym "SMART-FUNCTION-")))
(setf
(symbol-function fun)
(let ((counter 0) (funfun (eval body)))
(lambda (&rest args)
(declare (dynamic-extent args))
(when (> (incf counter) *threshold*)
(unless (compiled-function-p funfun)
(setq funfun (compile nil body)))
(setf (symbol-function fun) funfun))
(apply funfun args))))
fun))

(defmacro smart-lambda ((&rest args) &body body)
`(make-smart-function `(lambda ,',args ,',@body)))


Test:

[4]> (defparameter x (smart-lambda (a b c) (list a b c)))
X
[5]> (symbol-function x)
#<FUNCTION :LAMBDA (&REST ARGS) (DECLARE (DYNAMIC-EXTENT ARGS))
(WHEN (> (INCF COUNTER) *THRESHOLD*)
(UNLESS (COMPILED-FUNCTION-P FUNFUN) (SETQ FUNFUN (COMPILE NIL BODY)))
(SETF (SYMBOL-FUNCTION FUN) FUNFUN))
(APPLY FUNFUN ARGS)>
[6]> (funcall x 1 2 3)
(1 2 3)
[7]> (funcall x 1 2 3)
(1 2 3)
[8]> (symbol-function x)
#<FUNCTION :LAMBDA (&REST ARGS) (DECLARE (DYNAMIC-EXTENT ARGS))
(WHEN (> (INCF COUNTER) *THRESHOLD*)
(UNLESS (COMPILED-FUNCTION-P FUNFUN) (SETQ FUNFUN (COMPILE NIL BODY)))
(SETF (SYMBOL-FUNCTION FUN) FUNFUN))
(APPLY FUNFUN ARGS)>
[9]> *threshold*
10
[10]> (symbol-function x)
#<FUNCTION :LAMBDA (&REST ARGS) (DECLARE (DYNAMIC-EXTENT ARGS))
(WHEN (> (INCF COUNTER) *THRESHOLD*)
(UNLESS (COMPILED-FUNCTION-P FUNFUN) (SETQ FUNFUN (COMPILE NIL BODY)))
(SETF (SYMBOL-FUNCTION FUN) FUNFUN))
(APPLY FUNFUN ARGS)>
[11]> (funcall x 1 2 3)
(1 2 3)
[12]> (funcall x 1 2 3)
(1 2 3)
[13]> (funcall x 1 2 3)
(1 2 3)
[14]> (funcall x 1 2 3)
(1 2 3)
[15]> (funcall x 1 2 3)
(1 2 3)
[16]> (funcall x 1 2 3)
(1 2 3)
[17]> (funcall x 1 2 3)
(1 2 3)
[18]> (funcall x 1 2 3)
(1 2 3)
[19]> (funcall x 1 2 3)
(1 2 3)
[20]> (funcall x 1 2 3)
(1 2 3)
[21]> (funcall x 1 2 3)
(1 2 3)
[22]> (symbol-function x)
#<COMPILED-FUNCTION NIL>
[23]> (funcall x 1 2 3)
(1 2 3)
From: Pascal Costanza on
Kaz Kylheku wrote:
> On 2009-11-03, Pascal Costanza <pc(a)p-cos.net> wrote:
>> If you want your code to be very sophisticated, you can actually also
>> implement your own version that uses interpreted code by default, counts
>> how often a particular runtime-generated function is called, and after a
>> certain threshold, compiles into a more efficient version (under the
>> assumption that it will continue to be called even more often).
>>
>> Here is a sketch [untested]:
>>
>> (defclass smart-function (funcallable-standard-object) ()
>> (:metaclass funcallable-standard-class))
>>
>> (defparameter *threshold* 10)
>>
>> (defun make-smart-function (body)
>> (let ((fun (make-instance 'smart-function)))
>> (set-funcallable-instance-function
>> fun
>> (let ((counter 0) (funfun (eval body)))
>> (lambda (&rest args)
>> (declare (dynamic-extent args)
>> (when (> (incf counter) *threshold*)
>> (unless (compiled-function-p funfun)
>> (setq funfun (compile nil body)))
>> (set-funcallable-instance-function fun funfun))
>> (apply funfun args))))
>> fun))
>>
>> (defmacro smart-lambda ((&rest args) &body body)
>> `(make-smart-function `(lambda ,args ,@body)))
>>
>>
>> Yes, this requires the CLOS MOP. ;)
>
> Uh, you can do this with the existing standard funcallable objects. Namely,
> instances of this highly curious SYMBOL class, which have a standard
> slot given by the accessor SYMBOL-FUNCTION. :)

In principle yes, but unfortunately, there is a border case that doesn't
quite work with symbols as functions:

(defclass my-fun (funcallable-standard-object)
()
(:metaclass funcallable-standard-class))

(defmethod initialize-instance :after ((f my-fun) &key)
(set-funcallable-instance-function f (lambda () 42)))

* (setf (symbol-function 'foo) (make-instance 'my-fun))

#<MY-FUN {1002F44C09}>
* (foo)

42
* (setf (symbol-function 'bar) 'foo)
; in: LAMBDA NIL
; (FUNCALL #'(SETF SYMBOL-FUNCTION) #:NEW671
'CLOSER-COMMON-LISP-USER::BAR)
; --> SB-C::%FUNCALL
; ==>
; (#<SB-C::GLOBAL-VAR
; :%SOURCE-NAME (SETF SYMBOL-FUNCTION)
; :TYPE #<SB-KERNEL:FUN-TYPE (FUNCTION #'SYMBOL #)>
; :WHERE-FROM :DECLARED
; :KIND :GLOBAL-FUNCTION {1002F4A281}>
; #:NEW671 'CLOSER-COMMON-LISP-USER::BAR)
;
; caught WARNING:
; Asserted type FUNCTION conflicts with derived type
; (VALUES (MEMBER FOO) &OPTIONAL).
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition

debugger invoked on a TYPE-ERROR: The value FOO is not of type FUNCTION.


Some Common Lisp implementations accept this, some don't. Those that
don't accept this are right not to do so, according to the HyperSpec. (I
think this is an unnecessary restriction, symbols should be accepted as
symbol-functions, but that's maybe just me... ;)


Pascal

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Captain Obvious on
??>> Might have better performance. If you use &rest and then apply
??>> I guess most compilers will make a list, that is, you have more
??>> consing.
??>>
??>> (lambda (&rest args)
??>> (let ((fn ....))
??>> (apply fn args)))

KK> It's a poor Lisp compiler which conses a list for code which only
KK> passes the &rest argument down to another function with apply.

Can you please show an example of a compiler which optimizes it?

KK> You can't do that with a lambda closure, because a lambda closure
KK> cannot replace future calls to itself with a different closure.

That's why we have not one closure, but two closures, with first
calling the second one -- then we can replace second closure with
a compiled version when we would want to. There is overhead
of indirection, but arguably it is very low.

Here's code by Pascal:

(defparameter *threshold* 10)

(defun make-smart-function (body)
(let ((counter 0) fun funfun)
(setq funfun (eval body)
fun (lambda (&rest args)
(declare (dynamic-extent args))
(when (> (incf counter) *threshold*)
(unless (compiled-function-p funfun)
(setq funfun (compile nil body))
(setq fun funfun)))
(apply funfun args)))
(lambda (&rest args)
(declare (dynamic-extent args))
(apply fun args))))

(defmacro smart-lambda ((&rest args) &body body)
`(make-smart-function (lambda ,args ,@body)))

Let's see does it go over your head or not.