Возможность восстановления источника из функции зависит от реализации и уровня отладки вашей среды. В реализациях 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))
Слабые ха sh таблицы
Слабые ссылки также зависят от реализации, но вы можете использовать систему 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))
Метакласс
Если вы хотите избежать слабых таблиц ha sh, вы можете также обернуть свою функцию в другой объект, который может действовать как функция ( 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