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):注意
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 ,如果使用不当(不是列表),你会得到一个编译时错误,而且它更精确。