Macros 通用Lisp宏让curry-不工作

Macros 通用Lisp宏让curry-不工作,macros,common-lisp,Macros,Common Lisp,我发现自己调用了很多方法,这些方法的第一个参数是来自给定类的复杂对象。 虽然使用插槽和访问器很有用,但通用方法不能以这种方式绑定。所以我想:如果我们可以在本地curry任何函数,插槽+访问器+泛型函数+函数都可以用相同的结构来处理 我要清理的代码示例: (defun clox-string (scanner) "Parse string into a token and add it to tokens" (loop while (and (char/= #\" (peek scanne

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

我要清理的代码示例:

(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中模仿这一点,但我通常会得到比Java更详细、可读性更低的代码——特别是在大量使用此类时)。正如在CL方法中不属于类一样,您最终会反复声明此类参数。这样会更好一些:

(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)))))
宏的草图(不工作):

编辑(添加):请注意,
scanner
是一个类;start、source、line等,具有相同名称的插槽的访问器;将标记添加到具有多个参数的泛型函数中,推进具有一个参数的泛型方法:

(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)
  • 只有当您有遮挡/碰撞某物的危险,或者为了确保计算顺序最不令人惊讶时,才需要使用
    gensym
    ,但在您的情况下,您实际上希望使用咖喱版本遮挡原始名称,因此仅使用原始名称是有意义的
  • 如果要有多个参数,应使用
    apply
  • 由于知道函数位于函数名称空间中,因此需要调用
    #symbol
    ,而不是
    symbol
  • 我在原型中使用了
    (&rest函数)
    而不是
    函数
    ,使用不当(不是列表)会导致编译时错误,而且更精确
您所说的“虽然插槽和访问器很有用,但通用方法不能以这种方式绑定”是什么意思?使用访问器有点冗长,但它似乎是可行的:另外,请注意,“crafting解释器”在不同的有效解释器之间提供了一种可能的体系结构,尤其是呈现的体系结构更适合Java语言。如果翻译有点太直译,您可能没有惯用的Lisp(尽管这个练习很有趣)@coredump:我的意思是其中一些是访问器(如line或source),一些是泛型函数,如(defmethod add token((scanner scanner)(token type token type))…)。我认为使用访问器只适用于定义:defclass中的访问器?例如:(defclass scanner()((source:initarg:source:accessor-source)(…))?您的评论仍然是#1,看看您的代码,您的意思是可以在其他地方定义访问器,或者defmethods通常也可以用作访问器?(pastebin.com/y02JqdjW)?:是的,一开始我也很惊讶,但是accessor可以是任何函数,宏只允许在符号后面隐藏函数调用。第三条评论:所有令牌都与一个正则表达式相关联,并且它们都作为一个大的替换组合(正则表达式中的“|”)与命名寄存器(顺序很重要,第一场比赛获胜)。其余的只是调用字符串上的regex匹配,并从命名寄存器(捕获组)中找到读取的令牌类型。这只是为了说明该方法与Java中所示的方法有何不同。SBCL在您的示例中抱怨将
-
绑定到本地函数<代码>编译时错误:绑定时违反了包COMMON-LISP上的锁定-在包COMMON-LISP-USER中作为本地函数。@MartinBuchmann谢谢。我已经更改了函数以避免出现这种情况,但是如果需要的话,有一些方法可以解锁并避免出现这种情况。这不在这个答案的范围之内:)这真是太好了!它起作用了!但是,我的新方法无法设置:s@Alberto一切都可以修复,但不是在同一个问题上。哈哈,我肯定会打开一个新方法,然后在这里发布链接,谢谢:)
(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)