我正在为宏lambda列表尝试不同的绑定模型。
编辑:实际上我的测试宏的lambda列表始终是(&rest ...)
。这意味着我正在'解构'参数列表而不是lambda列表。我试图找到一个解决方案,用于将可选的键参数与rest / body与键参数组合在一起 - 这两种组合在Common Lisp标准实现中都不起作用。
所以我有不同的函数给我一个绑定列表,其语法与'let'使用的语法相同。
E.g:
(build-bindings ...) => ((first 1) middle (last "three"))
现在我想在我的测试宏中使用一个简单的宏,将这样的列表输入'let'。
如果我有一个文字列表,这是微不足道的:
(defmacro let-list (_list &rest _body)
`(let ,_list ,@_body))
(let-list ((a 236)) a) => 236
但这与普通的'let'相同。
我想要的是生成列表的相同内容。
所以,例如。
(let-list (build-bindings ...)
(format t "first: ~s~%" first)
last)
带有(build-bindings ...)
的,在与调用(let-list ...)
相同的词汇范围内进行评估,返回
((first 1) middle (last "three"))
宏的扩展应该是
(let
((first 1) middle (last "three"))
(format t "first: ~s~%" first)
last)
并打印1
并返回"three"
。
知道如何实现这个目标吗?
编辑(使问题更加通用):
如果我有(symbol value)
对的列表,即let
对其绑定列表所需的语法相同,例如((one 1) (two 'two) (three "three"))
,有没有办法编写一个宏来创建符号的词法绑定,并为其&rest
/ &body
参数提供值?
这似乎是约书亚指出的一个可能的解决方案:
(let ((list_ '((x 23) (y 6) z)))
(let
((symbols_(loop for item_ in list_
collect (if (listp item_) (car item_) item_)))
(values_ (loop for item_ in list_
collect (if (listp item_) (cadr item_) nil))))
(progv symbols_ values_
(format t "x ~s, y ~s, z ~s~%" x y z))))
evaluates to:
;Compiler warnings :
; In an anonymous lambda form: Undeclared free variable X
; In an anonymous lambda form: Undeclared free variable Y
; In an anonymous lambda form: Undeclared free variable Z
x 23, y 6, z NIL
我还可以轻松地重新安排我的build-bindings
函数,以返回所需的两个列表。
一个问题是,如果变量从未被声明为特殊,则编译器会发出警告。
另一个问题是,如果动态绑定的变量也用在周围的词法绑定中,它们会被词法绑定所遮蔽 - 如果它们从未被声明为特殊的那样:
(let ((x 47) (y 11) (z 0))
(let ((list_ '((x 23) (y 6) z)))
(let
((symbols_(loop for item_ in list_
collect (if (listp item_) (car item_) item_)))
(values_ (loop for item_ in list_
collect (if (listp item_) (cadr item_) nil))))
(progv symbols_ values_
(format t "x ~s, y ~s, z ~s~%" x y z)))))
evaluates to:
x 47, y 11, z 0
更好的方法是:
(let ((x 47) (y 11) (z 0))
(locally
(declare (special x y))
(let ((list_ '((x 23) (y 6) z)))
(let
((symbols_(loop for item_ in list_
collect (if (listp item_) (car item_) item_)))
(values_ (loop for item_ in list_
collect (if (listp item_) (cadr item_) nil))))
(progv symbols_ values_
(format t "x ~s, y ~s, z ~s~%" x y z))))))
evaluates to:
;Compiler warnings about unused lexical variables skipped
x 23, y 6, z NIL
目前我无法看到动态progv
绑定是否存在其他问题。
但progv
包裹在locally
的整个辣酱玉米饼馅,其中所有符号都被声明为宏的特殊哭声 - 由于同样的原因,这种情况再次无法实现let-list
不能工作:(
可能性是一种我不知道的宏 - lambda-list解构 - 钩子。
我必须研究destructuring-bind
的实现,因为该宏做了我想做的事情。也许这会让我感到高兴;)
答案 0 :(得分:4)
所以第一次(不正确)的尝试看起来像这样:
(defun build-bindings ()
'((first 1) middle (last "three")))
(defmacro let-list (bindings &body body)
`(let ,bindings
,@body))
然后你可以尝试做类似的事情:
(let-list (build-bindings)
(print first))
当然,这不会起作用,因为宏扩展会在结果让中留下(构建绑定)形式,在它的位置不会被评估:
CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings)
(print first))))
(LET (BUILD-BINDINGS)
(PRINT FIRST))
问题是您希望 macroexpansion 时的构建绑定的结果以及之前的 整个代码运行。现在,在这个例子中, build-bindings 可以在宏扩展时运行,因为它没有对任何参数做任何事情(记得我在评论中问了什么参数是?)。这意味着您可以在宏展开中实际评估:
(defmacro let-list (bindings &body body)
`(let ,(eval bindings)
,@body))
CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings)
(print first))))
(LET ((FIRST 1) MIDDLE (LAST "three"))
(PRINT FIRST))
现在这样可行,只要它将第一个,中间和最后绑定到 1 , 零,"三个" 。但是,如果构建绑定实际上需要一些在宏展开时无法获得的参数,那么您将失去运气。首先,可以获取宏扩展时可用的参数(例如,常量):
(defun build-bindings (a b &rest cs)
`((first ',a) (middle ',b) (last ',cs)))
CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings 1 2 3 4 5)
(print first))))
(LET ((FIRST '1) (MIDDLE '2) (LAST '(3 4 5)))
(PRINT FIRST))
您也可以在其中显示一些变量:
(defun build-bindings (x ex y why)
`((,x ,ex) (,y ,why)))
CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings 'a 'ay 'b 'bee)
(print first))))
(LET ((A AY) (B BEE))
(PRINT FIRST))
例如,你不能做类似的事情:
(let ((var1 'a)
(var2 'b))
(let-list (build-bindings var1 'ay var2 'bee)
(print first))
因为(let-list(build-bindings ...)...)是之前的,而任何此代码实际上都是执行。这意味着当 var1 和 var2时,您正在尝试评估(构建绑定var1' ay var2' bee) 并未绑定任何值。
Common Lisp首先全部其宏扩展,然后评估代码。这意味着在宏扩展时不可用于运行时之前不可用的值。
现在,即使我说Common Lisp首先完成所有宏扩展,然后评估代码,上面的代码实际上在宏扩展时使用 eval 来更早地获得一些额外的评估。我们也可以在另一个方向做事;我们可以在运行时使用编译。这意味着我们可以生成lambda函数并根据运行时提供的代码(例如,变量名称)对其进行编译。我们实际上可以在没有的情况下使用宏来执行:
(defun %dynamic-lambda (bindings body)
(flet ((to-list (x) (if (listp x) x (list x))))
(let* ((bindings (mapcar #'to-list bindings))
(vars (mapcar #'first bindings))
(vals (mapcar #'second bindings)))
(apply (compile nil `(lambda ,vars ,@body)) vals))))
CL-USER> (%dynamic-lambda '((first 1) middle (last "three"))
'((list first middle last)))
;=> (1 NIL "three")
这将编译一个lambda表达式,该表达式在运行时从正文和绑定列表创建。编写一个引用麻烦的宏观并不困难:
(defmacro let-list (bindings &body body)
`(%dynamic-lambda ,bindings ',body))
CL-USER> (let-list '((first 1) middle (last "three"))
(list first middle last))
;=> (1 NIL "three")
CL-USER> (macroexpand-1 '(let-list (build-bindings)
(list first middle last)))
;=> (%DYNAMIC-LAMBDA (BUILD-BINDINGS) '((LIST FIRST MIDDLE LAST)))
CL-USER> (flet ((build-bindings ()
'((first 1) middle (last "three"))))
(let-list (build-bindings)
(list first middle last)))
;=> (1 NIL "three")
这为您提供了在运行时创建的绑定列表中的真正词法变量。当然,因为编译是在运行时发生的,所以您将无法访问词法环境。这意味着您正在编译成函数的主体无法访问"周围的"词汇范围。 E.g:
CL-USER> (let ((x 3))
(let-list '((y 4))
(list x y)))
; Evaluation aborted on #<UNBOUND-VARIABLE X {1005B6C2B3}>.
如果您不需要词法变量,但可以使用特殊(即动态范围)变量,则可以使用 progv 在运行时建立绑定。这看起来像是:
(progv '(a b c) '(1 2 3)
(list c b a))
;;=> (3 2 1)
如果运行它,你可能会得到一些警告,因为在编译表单时,没有办法知道a,b和c应该是特殊变量。您可以使用本地添加一些特殊声明:
(progv '(a b c) '(1 2 3)
(locally
(declare (special a b c))
(list c b a)))
;;=> (3 2 1)
当然,如果您正在执行此操作,那么您必须事先了解变量完全您首先想要避免的变量。但是,如果您愿意提前知道变量的名称(并且your comments看起来可能没问题),那么您实际上可以使用词汇变量。
如果您愿意说明变量的内容,但仍希望在运行时动态计算其值,则可以相对轻松地完成。首先,让我们编写直接版本(没有宏):
;; Declare three lexical variables, a, b, and c.
(let (a b c)
;; Iterate through a list of bindings (as for LET)
;; and based on the name in the binding, assign the
;; corresponding value to the lexical variable that
;; is identified by the same symbol in the source:
(dolist (binding '((c 3) (a 1) b))
(destructuring-bind (var &optional value)
(if (listp binding) binding (list binding))
(ecase var
(a (setf a value))
(b (setf b value))
(c (setf c value)))))
;; Do something with the lexical variables:
(list a b c))
;;=> (1 NIL 3)
现在,编写一个macrofied版本并不难。这个版本并不完美,(例如,可能存在名称的卫生问题,并且身体中的声明不起作用(因为身体在某些东西之后被拼接)。它是一个开始但是:
(defmacro computed-let (variables bindings &body body)
(let ((assign (gensym (string '#:assign-))))
`(let ,variables
(flet ((,assign (binding)
(destructuring-bind (variable &optional value)
(if (listp binding) binding (list binding))
(ecase variable
,@(mapcar (lambda (variable)
`(,variable (setf ,variable value)))
variables)))))
(map nil #',assign ,bindings))
,@body)))
(computed-let (a b c) '((a 1) b (c 3))
(list a b c))
;;=> (1 NIL 3)
使这个更干净的一种方法是完全避免赋值,并且计算值直接提供绑定的值:
(defmacro computed-let (variables bindings &body body)
(let ((values (gensym (string '#:values-)))
(variable (gensym (string '#:variable-))))
`(apply #'(lambda ,variables ,@body)
(let ((,values (mapcar #'to-list ,bindings)))
(mapcar (lambda (,variable)
(second (find ,variable ,values :key 'first)))
',variables)))))
此版本创建一个lambda函数,其中参数是指定的变量,body是提供的主体(因此正文中的声明位于适当的位置),然后将其应用于从结果中提取的值列表计算出的绑定。
因为我正在做一些&#34;解构&#34;参数(以不同的方式),我知道哪些参数必须存在或具有哪些参数 缺少可选参数和键参数时的默认值。所以 第一步,我得到一个值列表和一个标志是否可选 或关键论点存在或违约。在第二步我会 喜欢将这些值和/或present / default标志绑定到local 与他们合作的变量
这实际上开始听起来像你可以通过使用lambda函数或destructuring-bind与关键字参数来做你需要的。首先,请注意您可以使用任何符号作为关键字参数指示符。 E.g:
(apply (lambda (&key
((b bee) 'default-bee b?)
((c see) 'default-see c?))
(list bee b? see c?))
'(b 42))
;;=> (42 T DEFAULT-SEE NIL)
(destructuring-bind (&key ((b bee) 'default-bee b?)
((c see) 'default-see c?))
'(b 42)
(list bee b? see c?))
;;=> (42 T DEFAULT-SEE NIL)
因此,如果您只是将函数返回绑定作为关键字参数列表,那么在解构或函数应用程序中,您可以自动绑定相应的变量,分配默认值,并检查是否提供了非默认值。
答案 1 :(得分:0)
间接地采取行动:
一个解决方案,用于组合可选的键参数或 休息/身体与关键参数
您是否考虑过使用关键字子列表并非完全不常见的范例?
e.g。
(defmacro something (&key (first 1) second) &body body) ... )
或亚历山大的实际用途:
(defmacro with-output-to-file ((stream-name file-name
&rest args
&key (direction nil direction-p)
&allow-other-keys)
&body body)