如何将参数格式化为函数
How to format parameter as a function
很快我就有了一个函数 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 12 34 :test #'(lambda (a b) (+ a b)))
46 is the result of my test #'(lambda (a b) (+ a b))
遗憾的是,function-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)))
也许,我可以用不同的方式来表述这个问题。如何将 lambda 函数保存为文件中的槽实例,以便从任何 lisp 实现中检索它。
或者更具体地说,我想将一个插槽设置为非解释函数,以便调用它进行解释并跟踪 'source'.
我的临时 'solution' 是显式使用宏函数,例如:
(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
从函数恢复源代码的能力取决于环境的实现和调试级别。在编译代码的 Common Lisp 实现中,您需要优化调试以跟踪源代码。有时源只是定义函数的文件名和一个偏移量。
命名函数
如果您想跟踪函数,如果您将自己限制在 named 函数,则更容易移植。只需将源代码附加到符号的 属性 列表中,使用宏:
;; 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))
弱哈希表
弱引用也依赖于实现,但您可以使用 trivial-garbage
系统移植地使用它们,或者在该功能不可用时收到通知。
在这里,您将实际的函数对象附加到它的源代码(或任何对象,但这对数字或字符来说不是很好,因为它们通常是不可识别的):
;; 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.")
缺点是 :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)))
例如:
(let ((fn (lambda* (x y) (+ x y))))
(prog1 (funcall fn 3 4)
(format t "~&Calling ~a" (source fn))))
上面的 returns 7 并打印 Calling (LAMBDA (X Y) (+ X Y))
元类
如果你想避免弱哈希表,你也可以将你的函数包装在另一个对象中,它可以像一个函数(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-funcallable-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)))
最后,这种方法的有用之处在于可以在打印函数时自动打印代码,方法是为 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
你已经接近完成宏了。如果将 "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))
所以:
(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 #'+
很快我就有了一个函数 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 12 34 :test #'(lambda (a b) (+ a b)))
46 is the result of my test #'(lambda (a b) (+ a b))
遗憾的是,function-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)))
也许,我可以用不同的方式来表述这个问题。如何将 lambda 函数保存为文件中的槽实例,以便从任何 lisp 实现中检索它。
或者更具体地说,我想将一个插槽设置为非解释函数,以便调用它进行解释并跟踪 'source'.
我的临时 'solution' 是显式使用宏函数,例如:
(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
从函数恢复源代码的能力取决于环境的实现和调试级别。在编译代码的 Common Lisp 实现中,您需要优化调试以跟踪源代码。有时源只是定义函数的文件名和一个偏移量。
命名函数
如果您想跟踪函数,如果您将自己限制在 named 函数,则更容易移植。只需将源代码附加到符号的 属性 列表中,使用宏:
;; 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))
弱哈希表
弱引用也依赖于实现,但您可以使用 trivial-garbage
系统移植地使用它们,或者在该功能不可用时收到通知。
在这里,您将实际的函数对象附加到它的源代码(或任何对象,但这对数字或字符来说不是很好,因为它们通常是不可识别的):
;; 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.")
缺点是 :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)))
例如:
(let ((fn (lambda* (x y) (+ x y))))
(prog1 (funcall fn 3 4)
(format t "~&Calling ~a" (source fn))))
上面的 returns 7 并打印 Calling (LAMBDA (X Y) (+ X Y))
元类
如果你想避免弱哈希表,你也可以将你的函数包装在另一个对象中,它可以像一个函数(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-funcallable-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)))
最后,这种方法的有用之处在于可以在打印函数时自动打印代码,方法是为 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
你已经接近完成宏了。如果将 "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))
所以:
(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 #'+