Common Lisp 宏 let-curry - 不工作

Common Lisp macro let-curry - not working

我发现自己调用了很多方法,这些方法的第一个参数是来自给定 class 的复杂对象。 虽然 with-slots 和 with-accessors 很有用,但不能以这种方式绑定泛型方法。所以我想:如果我们可以在本地柯里化任何函数,槽 + 访问器 + 泛型函数 + 函数都可以用相同的结构来处理。

我要清理的代码示例:

(defun clox-string (scanner)
  "Parse string into a token and add it to tokens"
  (loop while (and (char/= #\" (peek scanner))
                   (not (at-end-p scanner)))
        do
           (if (char= #\Newline (peek scanner)) (incf (line scanner))
               (advance scanner)))
  (when (at-end-p scanner)
    (clox.error::clox-error (line scanner) "Unterminated string.")
    (return-from clox-string nil))
  (advance scanner) ;; consume closing "
  (add-token scanner 'STRING (subseq (source scanner)
                                     (1+ (start scanner))
                                     (1- (current scanner)))))

这样会更简洁(我在 CL https://craftinginterpreters.com/scanning.html#reserved-words-and-identifiers 中模仿了这一点,但我经常得到比 Java 中更冗长且可读性更差的代码 - 特别是在使用此 [=33= 时]很多)。由于 CL 方法不属于 classes,因此您最终会一遍又一遍地声明此类参数。这样会好一点:

(defun clox-string (scanner)
  "Parse string into a token and add it to tokens"
  (let-curry scanner (peek at-end-p line source start current advance add-token)
   (loop while (and (char/= #\" (peek))
                    (not (at-end-p)))
         do
            (if (char= #\Newline (peek)) (incf (line))
                (advance)))
   (when (at-end-p)
     (clox.error::clox-error (line) "Unterminated string.")
     (return-from clox-string nil))
   (advance) ;; consume closing "
   (add-token 'STRING (subseq (source)
                              (1+ (start))
                              (1- (current)))))

宏草图(无效):

;; Clearly not as I don't understand macros very well :) non-working code:
(defmacro let-curry (obj functions &body body)
  "Locally curry all functions"
  (let ((fn (gensym)))
    `(flet (loop
             for ,fn in ,functions
             collect (list ,fn (&rest args)
                           (funcall ,fn ,obj args))) 
       ,@body)))

编辑(添加):注意 scanner 是 class; start、source、line 等,同名槽的访问器; add-token 一个多参数的泛型函数,advance 一个单参数的泛型方法:

(defclass scanner ()
  ((source
    :initarg :source
    :accessor source)
   ...
   (...)))

(defmethod advance ((scanner scanner)) ...)
(defmethod add-token ((scanner scanner) token-type) ...)

有错误的更简单示例:

;; With 
(defun add (x y) (+ x y))

(defun mul (x y) (* x y))

;; I want to have this:
(let-curry 1000 (add mul)
  (print (add 3))
  (print (mul 3)))


;; expanding to:
(flet ((add (y) (add 1000 y))
       (mul (y) (mul 1000 y)))
  (print (add 3))
  (print (mul 3)))

;; but instead I'm getting:
Execution of a form compiled with errors.
Form:
  (FLET (LOOP
       FOR
       #1=#:G777
       IN
       (ADD MUL
         )
       COLLECT
       (LIST #1#
         (&REST ARGS)
         (FUNCALL #1# 1000 ARGS)))
  (PRINT (ADD 3))
  (PRINT (MUL 3)))
Compile-time error:
  The FLET definition spec LOOP is malformed.
   [Condition of type SB-INT:COMPILED-PROGRAM-ERROR]

谢谢!基本问题是:是否可以使这样的宏起作用?

您的版本没有扩展到您想要的,但是:

(flet (loop for #:g8307 in (add mul) collect (list #:g8307 (&rest args) (funcall #:g8307 1000 args))) 
  (print (add 3)) (print (mul 3)))

现在循环需要在宏扩展时完成。 这是一个工作版本:

(defmacro let-curry (obj (&rest functions) &body body)
  "Locally curry all functions"
  `(flet ,(loop for fn in functions
                collect `(,fn (&rest args)
                            (apply #',fn ,obj args))) 
     ,@body))

;; test it using add and mul from OP
(macroexpand-1 '(let-curry 10 (add mul) (list (add 5) (mul 5))))
;; ==> 
(flet ((add (&rest args) (apply #'add 10 args)) 
       (mul (&rest args) (apply #'mul 10 args))) 
  (list (add 5) (mul 5)))

(let-curry 10 (add mul) (list (add 5) (mul 5)))
;; ==> (15 50)
  • 仅当您处于 shadowing/colliding 危险或确保评估顺序最不令人惊讶时才需要使用 gensym,但在您的情况下,您实际上想用咖喱遮盖原始名称版本所以只使用原始名称是有意义的。
  • 如果你想有多个参数,你应该使用 apply
  • 因为您知道函数在函数命名空间中,所以您需要调用 #'symbol 而不是 symbol
  • 我在原型中完成了 (&rest functions) 而不是 functions,使用不当(不是列表)会导致编译时错误,而且更精确。