Как отформатировать параметр как функцию - PullRequest
2 голосов
/ 23 марта 2020

Вскоре у меня есть функция 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)))

Может быть, я могу сформулировать вопрос по-другому. Как сохранить лямбда-функцию в качестве экземпляра слота в файле, чтобы извлечь ее из любой реализации lisp.

Или, чтобы быть более точным c, я хотел бы установить слот как не интерпретированный функция для того, чтобы вызывать ее, чтобы она интерпретировалась как таковая и имела след «источника».

Мое временное «решение» - это явное использование макрос-функции, такой как:

(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 ]

3 голосов
/ 23 марта 2020

Возможность восстановления источника из функции зависит от реализации и уровня отладки вашей среды. В реализациях 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
2 голосов
/ 23 марта 2020

Вы почти там с макросом. Если вы объедините «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 #'+
...