【问题标题】:Why does this macro work one way and not the other?为什么这个宏以一种方式工作而不是另一种?
【发布时间】:2012-07-04 18:30:03
【问题描述】:

这是我尝试使用的“clx-user-callable.lisp”中定义的宏。

(in-package :clx-gui)

(defmacro get-callback-wrapper (callback)
  (declare (ignorable callback))
  (let* ((func-name (gensym))
         (wrapper-name (intern (format nil "WRAPPER-~a" func-name) )))
    `(defun ,wrapper-name (caller-instance)
       (funcall ,callback) ;; User callbacks wont have arguments
       (closemenu caller-instance))))

我用这种方式调用这个宏,它工作正常。

(in-package :clx-gui-test-app)

(create-user-menuitem "MyUserMenu" "MyEntryDialog"
                      (get-callback-wrapper 'my-callback))

(create-user-menuitem "MyUserMenu" "MyChoiceDialog"
                      (get-callback-wrapper 'my-callback2))

(create-user-menuitem "MyUserMenu" "MyMessageDialog"
                      (get-callback-wrapper 'my-callback3))

如果我以这种方式更改代码以使用宏,通过将回调的符号名称传递给调用宏的函数,它不会返回不同的包装函数,但总是返回相同的包装函数。调用宏的函数与宏定义在同一个文件和包中。

(in-package :clx-gui-test-app)
(create-user-menuitem "MyUserMenu" "MyEntryDialog" 'my-callback)
(create-user-menuitem "MyUserMenu" "MyChoiceDialog" 'my-callback2)
(create-user-menuitem "MyUserMenu" "MyMessageDialog" 'my-callback3)

我已尝试将包添加到宏定义中,但这没有帮助。

(wrapper-name (intern (format nil "WRAPPER-~a" func-name)
                      (symbol-package callback) )))

我做错了什么?

我正在使用 SBCL-1.0.57 和 Slime。

【问题讨论】:

  • 那是因为宏是在编译函数时展开的,而不是在调用时展开的。
  • (eval-when (:compile-toplevel :load-toplevel :execute) 包装你的create-user-menuitem 函数。
  • 请注意,defun/gensym 方法不能保证创建唯一的函数名称,b/c 两个 gensym 可以不是 eq 并且具有相同的打印值。如果您只是想定义一个永远不会再次使用的唯一函数名称,并且您并不真正关心名称,为什么不直接使用 lambda?
  • @claytontstanley:为什么两个 gensyms 会有相同的打印值?
  • 请注意,正如所写的那样,没有理由需要将其实现为宏。一个函数就可以了,b/c 你提供给它的唯一参数只是一个带引号的符号。

标签: common-lisp sbcl


【解决方案1】:
CL-USER>
(defparameter foo1 (gensym))
FOO1
CL-USER> 
foo1
#:G4619
CL-USER> 
(defparameter foo2 '#:G4619)
FOO2
CL-USER> 
foo2
#:G4619
CL-USER> 
(eq foo1 foo2)
NIL
CL-USER> 
~           

或者另一个有趣的练习:

(defmacro make-fun ()  
  `(defun ,(intern (format nil "WRAPPER-~a" (gensym))) ()
     'bar))

CL-USER> 
(make-fun)
WRAPPER-G4726
CL-USER> 
(make-fun)
WRAPPER-G4730
CL-USER> 
(make-fun)
WRAPPER-G4734
CL-USER> 
(make-fun)
WRAPPER-G4738
CL-USER> 
(defun WRAPPER-G4745 ()
  'foo)
WRAPPER-G4745
CL-USER> 
(make-fun)
WRAPPER-G4745
CL-USER> (wrapper-G4745)
BAR
CL-USER>

哦,伙计,我们刚刚重写了那个函数!

如果你想用某种前缀名称来标记 gensym,请在 gensym 调用中进行(作为可选参数)。但这一切只是一个练习,b/c 我仍然会在 OP 问题中使用 lambda。

这是一种 (IMO) 更简单的替代实现,应该可以满足您的需求:

(defun get-callback-wrapper (callback)
  (lambda (caller-instance)
    (funcall callback) 
    (closemenu caller-instance)))

这会生成我认为您需要的词法闭包。

【讨论】:

  • 用 (eval-when (:compile-toplevel :load-toplevel :execute) 包装您的 create-user-menuitem 函数。——塞巴斯蒂安·贝尼特斯 2 天前没有效果。
  • 我误解了宏的使用方式。我认为用不同的回调符号调用 create-user-menuitem 应该导致不同的回调函数,这对我来说是不正确的想法。事实证明,在 defun 中使用 lambda 是完成我尝试的正确方法。谢谢大家的帮助。
  • @pbowler 乐于提供帮助。我会鼓励你继续编写和学习更多关于常见 lisp 宏的知识。我关于这个主题的书籍推荐是 On Lisp 和 Let Over Lambda;您的原始帖子涉及 Let Over Lambda 中讨论的许多主题,仅供参考
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多