常见的Lisp宏let-curry-无法正常工作 [英] Common Lisp macro let-curry - not working

查看:57
本文介绍了常见的Lisp宏let-curry-无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现自己正在调用许多方法,这些方法的第一个参数是给定类中的复杂对象.尽管with-slots和with-accessors是有用的,但是通用方法不能以这种方式绑定.所以我想:如果我们可以在本地使用任何函数,那么插槽+访问器+泛型函数+函数都可以用相同的结构来解决.

I found myself calling lots of methods whose first argument is a complex object from a given class. Whilst with-slots and with-accessors are useful, generic methods cannot be bound in this way. So I thought: if we could locally curry any functions, slots + accessors + generic functions + functions could all be addressed with the same construct.

我要清理的代码示例:

(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相比,我常常得到更多冗长且可读性更差的代码-特别是在大量使用此类时.由于在CL方法中不属于类,所以最终要一遍又一遍地声明此类参数.这样会更好一点:

This would be cleaner (I'm imitating this in CL https://craftinginterpreters.com/scanning.html#reserved-words-and-identifiers but I often end up with more verbose and less readable code than in Java - specially when using this classes a lot). As in CL methods don't belong to classes you end up declaring such arguments over and over. This would be a bit better:

(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)))

EDIT(ADD):注意 scanner 是一个类;起始,来源,行等访问具有相同名称的插槽;add-token一个具有多个参数的泛型函数,并推进具有一个参数的泛型方法:

EDIT (ADD): Notice that scanner is a class; start, source, line, etc., accessors to the slots with the same name; add-token a generic function of more than one argument, advance a generic method of one argument:

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

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


带有错误的简单示例:


Simpler Example with error:

;; 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]

谢谢!基本问题是:可以使这种宏起作用吗?

Thanks! The basic question is: is it possible to make such macro work?

推荐答案

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

Your version didn't expand to what you wanted but:

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

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

Now the loop needs to be done at macro expansion time. Here is a working version:

(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函数)而不是原型中的 functions ,如果使用率不高(不是列表),则会出现编译时错误,并且更精确.
    • Using gensym is only needed if you are in danger of shadowing/colliding something or to ensure evaluation order is least surprising, but in your case you actually want to shadow the original names with the curried version so it makes sense to just use the original name.
    • If you want to have more than one argument you should use apply
    • since you know the function is in the function namespace you need to call #'symbol instead of symbol.
    • I've done (&rest functions) instead of functions in the prototype that with bad usage (not a list) you get a compile time error and it is more preciese.
    • 这篇关于常见的Lisp宏let-curry-无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆