【问题标题】:How to format parameter as a function如何将参数格式化为函数
【发布时间】:2020-03-23 10:44:50
【问题描述】:

很快我就有了一个函数 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 实现中检索它。

或者更具体地说,我想将一个槽设置为非解释函数,以便将其调用为这样解释并跟踪“源”。

我的临时“解决方案”是明确使用宏函数,例如:

(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

【问题讨论】:

  • 您可以尝试function-lambda-expression,但这取决于实现。另外,你可以看到this answer
  • 这是因为CCL编译一切,所以无法显示原始源代码。
  • @Renzo:编译不是问题。它只需要记录来源。

标签: format common-lisp


【解决方案1】:

从函数恢复源的能力取决于环境的实现和调试级别。在编译代码的 Common 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))

弱哈希表

弱引用也依赖于实现,但您可以使用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))))

上面返回 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

【讨论】:

    【解决方案2】:

    您几乎可以使用宏。如果将“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 #'+
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-30
      • 2012-02-14
      • 1970-01-01
      • 2019-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多