How to format parameter as a function

Viewed 99

Shortly I have a function foo:

(defun foo (a b &key test) 
  (format t "~S is the result of my test ~A" (funcall test a b) test))

then the result of the evaluation is:

(foo 12 34 :test #'(lambda (a b) (+ a b)))
46 is the result of my test #<Anonymous Function #x30200171D91F>

and I want

(foo 12 34 :test #'(lambda (a b) (+ a b)))
46 is the result of my test #'(lambda (a b) (+ a b))

Unfortunately, function-lambda-expression does not display any information in CCL.

The point is this is implementation-dependent.
For instance in CCL:

(describe #'(lambda (a b) (+ a b)))
#<Anonymous Function #x302000C49E1F>
Name: NIL
Arglist (analysis): (A B)
Bits: -528481792
Plist: (CCL::FUNCTION-SYMBOL-MAP (#(B A) . #(575 18 49 63 18 49)))

Maybe, I can formulate the question differently. How to save a lambda function as a slot instance in a file in order to retrieve it from any lisp implementation.

Or to be more specific, I would like to set a slot as a non-interpreted function in order to call it to be interpreted as such and have a trace of the 'source'.

My temporary 'solution' is to use explicitly a macro function such as:

(defmacro src (func) `(read-from-string (format nil "~A" ',func)))
(setf (my-slot my-class-object) (src #'(lambda (a b) (* a b))))
;; this stores the un-interpreted function such as
(my-slot my-class-object)
;; return 
#'(lambda (a b) (* a b))
;; then I can do
(funcall (my-slot my-class-object) 2 3)
6
2 Answers

The ability to restore the source from a function depends on the implementation and the debug level of your environment. In Common Lisp implementations that compiles code, you need to optimize for debugging to keep track of the source code. Sometimes the source is simply the filename where the function was defined, and an offset.

Named functions

If you want to keep track of functions, it is easier to do portably if you restrict yourself to named functions. Just attach the source code to the property list of the symbol, using a macro:

;; body should be a single form that returns a name, like "defun"
(defmacro with-source-code (&body body)
  (destructuring-bind (form) body
    (let ((name$ (gensym)))
      `(let ((,name$ ,form))
         (check-type ,name$ symbol)
         (setf (get ,name$ 'source-code) ',form)
         ,name$))))

;; get the code associated with the name
(defun source-code (name)
  (check-type name symbol)
  (get name 'source-code))

For example:

(with-source-code
  (defun my-test-fn (x y)
    (+ x y)))

(source-code 'my-test-fn)
=> (DEFUN MY-TEST-FN (X Y) (+ X Y))

Weak hash tables

Weak references are also implementation dependent, but you can use the trivial-garbage system to use them portably, or be notified when the feature is unavailable.

Here you attach the actual function object to its source code (or, any object, but this is not great for numbers or characters since they are usually not identifiable):

;; defines package "tg"
(ql:quickload :trivial-garbage)

(defparameter *source-map*
  (tg:make-weak-hash-table :test #'eq :weakness :key)
  "Map objects to their defining forms.")

The weakness is :key so that the garbage collector may remove the entry if the key (the object whose code we want to retrieve) is garbage collected. This should be enough to avoid keeping entries indefinitely.

(defmacro remember (form)
  (let ((value$ (gensym)))
    `(let ((,value$ ,form))
       (setf (gethash ,value$ *source-map*) ',form)
       ,value$)))

(defun source (object)
  (gethash object *source-map*))

For example, you can define a lambda* macro that remembers the anonymous function being defined:

(defmacro lambda* ((&rest args) &body body)
  `(remember (lambda ,args ,@body)))

For example:

(let ((fn (lambda* (x y) (+ x y))))
  (prog1 (funcall fn 3 4)
    (format t "~&Calling ~a" (source fn))))

The above returns 7 and prints Calling (LAMBDA (X Y) (+ X Y))

Metaclass

If you want to avoid weak hash tables, you can also wrap your function in another object, which can act like a function (a funcallable object), using the meta-object protocol.

In that case, you can use closer-mop to have a unified API to work with the Meta-Object Protocol:

(ql:quickload :closer-mop)

You define a subclass of funcallable-standard-object that keep track of the source code, and the function (or closure) being called:

(defclass fn-with-code (c2mop:funcallable-standard-object)
  ((source :reader source-of :initarg :source))
  (:metaclass c2mop:funcallable-standard-class))

The object can be called like any other function, but for that you need to call set-funcallable-instance-function. We can do that after initializing the object, by definining the following method:

(defmethod initialize-instance :after ((f fn-with-code)
                                       &key function &allow-other-keys)
  (c2mop:set-funcallable-instance-function f function))

I also define a help function to build such an instance, given a function object and its source code:

(defun make-fn-with-code (function source)
  (make-instance 'fn-with-code :source source :function function))

Then, we can rewrite lambda* as follows:

(defmacro lambda* ((&rest args) &body body)
  (let ((code `(lambda ,args ,@body)))
    `(make-fn-with-code ,code ',code)))

Finally, what is useful with this approach is that the code can be printed automatically when the function is printed, by defining a method for print-object:

(defmethod print-object ((o fn-with-code) stream)
  (print-unreadable-object (o stream :type nil :identity nil)
    (format stream "FUN ~a" (source-of o))))

> (lambda* (x y) (* x y))
#<FUN (LAMBDA (X Y) (* X Y))>   ;; << printed as follow

You are nearly there with a macro. If you merge "foo" and "format-function" into one macro:

(defmacro format-result (a b &key test) 
     `(format t "~S is the result of my test ~A" 
                (funcall ,test ,a ,b) ',test))

so:

(FORMAT-RESULT 1 2 :test (lambda (a b) (+ a b)))
3 is the result of my test (LAMBDA (A B) (+ A B))
(FORMAT-RESULT 1 2 :test #'+)
3 is the result of my test #'+
Related