关于 Common Lisp 宏 let-curry:Common Lisp 宏 let-curry – not working

Common Lisp macro let-curry - not working

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

我要清理的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(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= #\
ewline (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 更冗长且可读性更低的代码 -特别是在大量使用此类时)。由于 CL 方法不属于类,您最终会一遍又一遍地声明此类参数。这样会好一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(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= #\
ewline (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)))))

宏草图(不工作):

1
2
3
4
5
6
7
8
9
;; 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)))

EDIT (ADD):注意 scanner 是一个类; start、source、line 等,同名插槽的访问器; add-token 一个多参数的泛型函数,推进一个参数的泛型方法:

1
2
3
4
5
6
7
8
9
(defclass scanner ()
  ((source
    :initarg :source
    :accessor source)
   ...
   (...)))

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

更简单的错误示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
;; 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]

谢谢!基本问题是:是否有可能使这样的宏工作?


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(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)
  • gensym 仅在您有阴影/碰撞某些东西的危险或确保评估顺序最不令人惊讶时才需要使用,但在您的情况下,您实际上希望使用 curried 版本隐藏原始名称,因此仅使用原名。
  • 如果你想有多个参数,你应该使用 apply
  • 因为您知道该函数位于函数命名空间中,所以您需要调用 #'symbol 而不是 symbol
  • 我在原型中完成了 (&rest functions) 而不是 functions ,如果使用不当(不是列表),你会得到一个编译时错误,而且它更精确。