Format 如何将参数格式化为函数

Format 如何将参数格式化为函数,format,common-lisp,Format,Common Lisp,很快我就有了一个函数foo: (defun foo (a b &key test) (format t "~S is the result of my test ~A" (funcall test a b) test)) 那么评估的结果是: (foo 12 34 :test #'(lambda (a b) (+ a b))) 46 is the result of my test #<Anonymous Function #x30200171D91F> 不幸的是,函

很快我就有了一个函数foo:

(defun foo (a b &key test) 
  (format t "~S is the result of my test ~A" (funcall test a b) test))
那么评估的结果是:

(foo 12 34 :test #'(lambda (a b) (+ a b)))
46 is the result of my test #<Anonymous Function #x30200171D91F>
不幸的是,
函数lambda expression
在CCL中不显示任何信息

关键是这取决于实现。
例如,在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)))

从函数还原源代码的能力取决于环境的实现和调试级别。在编译代码的常见Lisp实现中,需要优化调试以跟踪源代码。有时,源文件只是定义函数的文件名和偏移量

命名函数 如果您想跟踪函数,那么将自己限制在命名函数中,就更容易实现可移植性。只需使用宏将源代码附加到符号的属性列表:

;; 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))
例如:

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

(source-code 'my-test-fn)
=> (DEFUN MY-TEST-FN (X Y) (+ X Y))
(let ((fn (lambda* (x y) (+ x y))))
  (prog1 (funcall fn 3 4)
    (format t "~&Calling ~a" (source fn))))
弱哈希表 弱引用也依赖于实现,但是您可以使用
普通垃圾
系统以可移植的方式使用它们,或者在功能不可用时收到通知

在这里,您可以将实际的函数对象附加到其源代码(或任何对象,但这对于数字或字符来说并不好,因为它们通常无法识别):

缺点是
:key
,这样,如果key(我们要检索其代码的对象)被垃圾收集,垃圾收集器可能会删除该条目。这应该足以避免无限期地保留条目

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

(defun source (object)
  (gethash object *source-map*))
例如,您可以定义一个
lambda*
宏来记住所定义的匿名函数:

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

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

(source-code 'my-test-fn)
=> (DEFUN MY-TEST-FN (X Y) (+ X Y))
(let ((fn (lambda* (x y) (+ x y))))
  (prog1 (funcall fn 3 4)
    (format t "~&Calling ~a" (source fn))))
上面返回7并打印
调用(LAMBDA(xy)(+xy))

元类 如果希望避免弱哈希表,还可以使用元对象协议将函数包装到另一个对象中,该对象的行为类似于函数(funcallable对象)

在这种情况下,您可以使用
closer mop
来使用统一的API来处理元对象协议:

(ql:quickload :closer-mop)
您定义了
funcallable standard object
的子类,该子类跟踪源代码和调用的函数(或闭包):

(defclass fn-with-code (c2mop:funcallable-standard-object)
  ((source :reader source-of :initarg :source))
  (:metaclass c2mop:funcallable-standard-class))
可以像调用任何其他函数一样调用该对象,但为此需要调用
set functualable instance function
。我们可以在初始化对象后,通过定义以下方法来完成此操作:

(defmethod initialize-instance :after ((f fn-with-code)
                                       &key function &allow-other-keys)
  (c2mop:set-funcallable-instance-function f function))
给定函数对象及其源代码,我还定义了一个帮助函数来构建这样一个实例:

(defun make-fn-with-code (function source)
  (make-instance 'fn-with-code :source source :function function))
然后,我们可以重写
lambda*
,如下所示:

(defmacro lambda* ((&rest args) &body body)
  (let ((code `(lambda ,args ,@body)))
    `(make-fn-with-code ,code ',code)))
最后,这种方法的有用之处在于,通过定义
打印对象的方法,可以在打印函数时自动打印代码:

(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
(defmethod打印对象((o fn带代码)流)
(打印不可读对象(o流:类型nil:标识nil)
(格式流“FUN~a”(o的源)))
>(λ*(xy)(*xy))

# ;; 用宏就快到了。如果将“foo”和“format function”合并到一个宏中:

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

因此:


您可以尝试,但这取决于实现。另外,您可以看到。这是因为CCL编译所有内容,因此它无法显示原始源代码。@Renzo:编译不是问题。它只需要记录来源。
(defmacro format-result (a b &key test) 
     `(format t "~S is the result of my test ~A" 
                (funcall ,test ,a ,b) ',test))

(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 #'+