Как избежать eval в defmacro? - PullRequest
2 голосов
/ 12 апреля 2019

Я пишу макрос, который принимает список лямбд для вызова и генерирует функцию.Лямбды всегда оцениваются в defun списке аргументов, но не в defmacro.Как я могу избежать вызова eval внутри defmacro?

Этот код работает:

(defmacro defactor (name &rest fns)
  (let ((actors (gensym)))
    `(let (;(,actors ',fns)
           (,actors (loop for actor in ',fns
                          collect (eval actor)))) ; This eval I want to avoid
       (mapcar #'(lambda (x) (format t "Actor (type ~a): [~a]~&" (type-of x) x)) ,actors)
       (defun ,name (in out &optional (pos 0))
         (assert (stringp in))
         (assert (streamp out))
         (assert (or (plusp pos) (zerop pos)))
         (loop for actor in ,actors
               when (funcall actor in out pos)
               return it)))))

;; Not-so-relevant use of defactor macros
(defactor invert-case
    #'(lambda (str out pos)
        (let ((ch (char str pos)))
          (when (upper-case-p ch)
            (format out "~a" (char-downcase ch))
            (1+ pos))))
  #'(lambda (str out pos)
      (let ((ch (char str pos)))
        (when (lower-case-p ch)
          (format out "~a" (char-upcase ch))
          (1+ pos)))))

Этот код оценивается, как ожидается:

Actor (type FUNCTION): [#<FUNCTION (LAMBDA (STR OUT POS)) {100400221B}>]
Actor (type FUNCTION): [#<FUNCTION (LAMBDA (STR OUT POS)) {100400246B}>]
INVERT-CASE

Иего использование:

;; Complete example
(defun process-line (str &rest actors)
  (assert (stringp str))
  (with-output-to-string (out)
    (loop for pos = 0 then (if success success (1+ pos))
          for len = (length str)
          for success = (loop for actor in actors
                              for ln = len
                              for result = (if (< pos len)
                                               (funcall actor str out pos)
                                               nil)
                              when result return it)
          while (< pos len)
          unless success do (format out "~a" (char str pos)))))

(process-line "InVeRt CaSe" #'invert-case) ; evaluates to "iNvErT cAsE" as expected

Без eval, defactor выше оценивается как:

Actor (type CONS): [#'(LAMBDA (STR OUT POS)
                        (LET ((CH (CHAR STR POS)))
                          (WHEN (UPPER-CASE-P CH)
                            (FORMAT OUT ~a (CHAR-DOWNCASE CH))
                            (1+ POS))))]
Actor (type CONS): [#'(LAMBDA (STR OUT POS)
                        (LET ((CH (CHAR STR POS)))
                          (WHEN (LOWER-CASE-P CH)
                            (FORMAT OUT ~a (CHAR-UPCASE CH))
                            (1+ POS))))]

, а все остальное, очевидно, не работает.

Если я преобразую defmacro в defun, ему не нужно eval:

(defun defactor (name &rest fns)
  (defun name (in out &optional (pos 0))
    (assert (stringp in))
    (assert (streamp out))
    (assert (or (plusp pos) (zerop pos)))
    (loop for actor in fns
          when (funcall actor in out pos)
          return it)))

Однако он всегда определяет функцию name вместо переданного аргумента имени функции (который долженв кавычках).

Можно ли написать defactor с возможностью передачи имени функции в отличие от defun версии и без eval в macro версии?

Ответы [ 2 ]

6 голосов
/ 12 апреля 2019

Вы делаете вещи более сложными, чем необходимо с первым loop ... просто соберите параметры вместо

(defmacro defactor (name &rest fns)
  (let ((actors (gensym)))
    `(let ((,actors (list ,@fns)))
       (mapcar #'(lambda (x) (format t "Actor (type ~a): [~a]~&" (type-of x) x)) ,actors)
       (defun ,name (in out &optional (pos 0))
         (assert (stringp in))
         (assert (streamp out))
         (assert (or (plusp pos) (zerop pos)))
         (loop for actor in ,actors
               when (funcall actor in out pos)
               return it)))))
1 голос
/ 13 апреля 2019

В основном это не должен быть макрос как есть.В основном вы можете использовать вспомогательную функцию:

(defun make-actor (&rest funs)
  (lambda (in out &optional (pos 0)
    (loop for actor in funs
      when (funcall actor in out pos) return it)))

и написать простой макрос:

(defmacro defactor (name &rest funs)
  `(let ((f (make-actor ,@funs)))
      (defun ,name (in out &optional (pos 0)) (funcall f in out pos))))

Однако с точки зрения выразительности это мало что дает (вы практически называете макрос какфункция) или эффективность (компилятор должен быть достаточно умен, чтобы понять, как улучшить код, используя множество сложных вещей).


Вот еще один способ реализовать что-то вроде этого:

(defmacro defactor (name (in out pos) &rest actors)
  (let ((inv (gensym "IN"))
        (outv (gensym "OUT"))
        (posv (gensym "POS")))
    `(defun ,name (,inv ,outv &optional (,posv 0))
        ;; TODO: (declare (type ...) ...)
        (or ,@(loop for form in actors 
                 collect `(let ((,in ,inv) (,out ,outv) (,pos ,posv)) ,form)))))

А затем используйте его как:

(defactor invert-case (in out pos)
  (let ((ch (char str pos)))
    (when (upper-case-p ch)
      (format out "~a" (char-downcase ch))
      (1+ pos)))
  (let ((ch (char str pos)))
    (when (lower-case-p ch)
      (format out "~a" (char-upcase ch))
      (1+ pos))))
...