Macros 使用Common Lisp中的宏生成TYPECASE
我有一个两个元素子列表的列表,它们将在程序的过程中改变和增长。我想编写一个宏,它接受一个键并动态生成一个Macros 使用Common Lisp中的宏生成TYPECASE,macros,common-lisp,Macros,Common Lisp,我有一个两个元素子列表的列表,它们将在程序的过程中改变和增长。我想编写一个宏,它接受一个键并动态生成一个大小写,如下所示: ;; This is the List for saving CASE clauses (setf l '((number 2) (symbol 3))) ;; and i want to have the following expansion (typecase 'y (number 2) (symbol 3)) 我可以有一个宏,它只引用全局l: (def
大小写,如下所示:
;; This is the List for saving CASE clauses
(setf l '((number 2) (symbol 3)))
;; and i want to have the following expansion
(typecase 'y
(number 2)
(symbol 3))
我可以有一个宏,它只引用全局l
:
(defmacro m (x)
`(typecase ,x ,@l))
哪个会正确地展开
(m 'y) ;expands to (TYPECASE 'Y (number 2) (symbol 3))
但是,如何为列表l
编写带有参数的宏,以便它也能与其他列表一起工作
;; A macro which should generate the case based on the above list
(defmacro m (x l)
`(typecase ,x ,@l))
这不起作用,因为在参数列表i中,符号和对(m'y l)
的调用将扩展到(TYPECASE'y.l)
为了坚持typecase
机制,我的解决方法如下:
(setf types-x '(((integer 0 *) 38)
((eql neli) "Neli in X")
(symbol 39))
)
(setf types-y '(((eql neli) "Neli in Y")
((array bit *) "A Bit Vector")))
(defmacro m (x types-id)
(case types-id
(:x `(typecase ,x ,@types-x))
(:y `(etypecase ,x ,@types-y))))
(m 'neli :x) ;"Neli in X"
(m 'neli :y) ;"Neli in Y"
(m 'foo :x) ;39
(defun m (x l)
(second (assoc-if (lambda (type)
(typep x type))
l)))
欢迎提供任何提示和注释。宏扩展发生在编译时,而不是运行时,因此,如果case子句列表在程序过程中发生更改,宏扩展将不会改变以反映它。
如果要动态选择未计算但可更改的值,可以在展开中使用assoc
,而不是case
:
(defmacro m (x l)
`(second (assoc ,x ,l)))
样本扩展:
(m x l)
->
(SECOND (ASSOC X L))
问题中值为l
且x
='x
的(assoc x l)
输出:
(let ((x 'x))
(m x l))
->
2
但是,如果您确实决定这样做,您可以简化事情并用函数替换宏:
(defun m (x l)
(second (assoc x l)))
问题编辑的更新:
替换assoc如下:
(setf types-x '(((integer 0 *) 38)
((eql neli) "Neli in X")
(symbol 39))
)
(setf types-y '(((eql neli) "Neli in Y")
((array bit *) "A Bit Vector")))
(defmacro m (x types-id)
(case types-id
(:x `(typecase ,x ,@types-x))
(:y `(etypecase ,x ,@types-y))))
(m 'neli :x) ;"Neli in X"
(m 'neli :y) ;"Neli in Y"
(m 'foo :x) ;39
(defun m (x l)
(second (assoc-if (lambda (type)
(typep x type))
l)))
宏扩展发生在编译时,而不是运行时,因此,如果case子句列表在程序过程中发生更改,宏扩展将不会更改以反映它。
如果要动态选择未计算但可更改的值,可以在展开中使用assoc
,而不是case
:
(defmacro m (x l)
`(second (assoc ,x ,l)))
样本扩展:
(m x l)
->
(SECOND (ASSOC X L))
问题中值为l
且x
='x
的(assoc x l)
输出:
(let ((x 'x))
(m x l))
->
2
但是,如果您确实决定这样做,您可以简化事情并用函数替换宏:
(defun m (x l)
(second (assoc x l)))
问题编辑的更新:
替换assoc如下:
(setf types-x '(((integer 0 *) 38)
((eql neli) "Neli in X")
(symbol 39))
)
(setf types-y '(((eql neli) "Neli in Y")
((array bit *) "A Bit Vector")))
(defmacro m (x types-id)
(case types-id
(:x `(typecase ,x ,@types-x))
(:y `(etypecase ,x ,@types-y))))
(m 'neli :x) ;"Neli in X"
(m 'neli :y) ;"Neli in Y"
(m 'foo :x) ;39
(defun m (x l)
(second (assoc-if (lambda (type)
(typep x type))
l)))
你不需要一个宏来完成你想要做的事情:使用一个函数
例如,给定
(defvar *type-matches*
'((float 0)
(number 1)
(t 3)))
然后
将对象与类型匹配:
> (type-match 1.0)
0
float
> (type-match 1)
1
number
您希望按类型对变量进行排序,例如:
(setf *type-matches* (sort *type-matches* #'subtypep :key #'car))
当然,您希望保持匹配的排序
如果您想延迟表单的执行,那么您可以执行类似的操作(这也处理类型排序):
你不需要一个宏来完成你想要做的事情:使用一个函数
例如,给定
(defvar *type-matches*
'((float 0)
(number 1)
(t 3)))
然后
将对象与类型匹配:
> (type-match 1.0)
0
float
> (type-match 1)
1
number
您希望按类型对变量进行排序,例如:
(setf *type-matches* (sort *type-matches* #'subtypep :key #'car))
当然,您希望保持匹配的排序
如果您想延迟表单的执行,那么您可以执行类似的操作(这也处理类型排序):
你面临的实际问题是如果你这样做
(setf l '((number 2) (symbol 3)))
已经在顶级,如果您评估l
,您不会超过
((number 2) (symbol 3))
因此,如果在宏中使用l
作为参数,则不能再进一步了
比这个好。但您需要的是在宏中再次计算此表单(在添加typecase
和预先计算的x
后修改)
这就是为什么@tfb建议编写一个函数来实际评估l
中指定类型的匹配。
因此,我们可以将他的类型匹配
函数视为l
中给出的类型规范的小型解释器
如果你做一个简单的(defm(xl)`(typecase,x,@l))
你面临的正是这个问题:
(macroexpand-1 '(m 1 l))
;; (typecase 1 . l)
但我们需要的是再次评估l
(defmacro m (x l)
`(typecase ,x ,@(eval l)))
这将产生实际期望的结果:
(macroexpand-1 '(m 1 l))
;; (TYPECASE 1 (NUMBER 2) (SYMBOL 3)) ;
;; T
;; and thus:
(m 1 l) ;; 2
到目前为止,它似乎奏效了。但在后脑勺的某个地方,它变得很痒,因为我们从书籍和社区中了解到:“不要在代码中使用eval
!!eval
是邪恶的
!”
尝试一下,你会发现它很快就会咬到你:
# try this in a new session:
(defmacro m (x l) `(typecase ,x ,@(eval l)))
;; m
;; define `l` after definition of the macro works:
(setf l '((number 2) (symbol 3)))
;; ((NUMBER 2) (SYMBOL 3))
(m 1 l)
;; 2 ;; so our `eval` can handle definitions of `l` after macro was stated
(m '(1 2) l)
;; NIL
;; even redefining `l` works!
(setf l '((number 2) (symbol 3) (list 4)))
;; ((NUMBER 2) (SYMBOL 3) (LIST 4))
(m 1 l)
;; 2
(m '(1 2) l)
;; 4 ;; and it can handle re-definitions of `l` correctly.
;; however:
(let ((l '((number 2) (symbol 3)))) (m '(1 2) l))
;; 4 !!! this is clearly wrong! Expected is NIL!
;; so our `eval` in the macro cannot handle scoping correctly
;; which is a no-go for usage!
;; but after re-defining `l` globally to:
(setf l '((number 2) (symbol 3)))
;; ((NUMBER 2) (SYMBOL 3))
(m '(1 2) l)
;; NIL ;; it behaves correctly
(let ((lst '((number 2) (symbol 3) (list 4)))) (m '(1 2) lst))
;; *** - EVAL: variable LST has no value
;; so it becomes clear: `m` is looking in the scoping
;; where it was defined - the global scope (the parent scope of `m` when `m` was defined or within the scope of `m`).
因此,结论是:
带有eval
的给定宏工作不正常!!
因为它不能处理本地范围
因此@tfb的答案——为l
编写一个小型求值函数可能是以正确、安全、正确的方式处理此问题的唯一方法。您面临的实际问题是,如果您这样做
(setf l '((number 2) (symbol 3)))
已经在顶级,如果您评估l
,您不会超过
((number 2) (symbol 3))
因此,如果在宏中使用l
作为参数,则不能再进一步了
比这个好。但您需要的是在宏中再次计算此表单(在添加typecase
和预先计算的x
后修改)
这就是为什么@tfb建议编写一个函数来实际评估l
中指定类型的匹配。
因此,我们可以将他的类型匹配
函数视为l
中给出的类型规范的小型解释器
如果你做一个简单的(defm(xl)`(typecase,x,@l))
你面临的正是这个问题:
(macroexpand-1 '(m 1 l))
;; (typecase 1 . l)
但我们需要的是再次评估l
(defmacro m (x l)
`(typecase ,x ,@(eval l)))
这将产生实际期望的结果:
(macroexpand-1 '(m 1 l))
;; (TYPECASE 1 (NUMBER 2) (SYMBOL 3)) ;
;; T
;; and thus:
(m 1 l) ;; 2
到目前为止,它似乎奏效了。但在后脑勺的某个地方,它变得很痒,因为我们从书籍和社区中了解到:“不要在代码中使用eval
!!eval
是邪恶的
!”
尝试一下,你会发现它很快就会咬到你:
# try this in a new session:
(defmacro m (x l) `(typecase ,x ,@(eval l)))
;; m
;; define `l` after definition of the macro works:
(setf l '((number 2) (symbol 3)))
;; ((NUMBER 2) (SYMBOL 3))
(m 1 l)
;; 2 ;; so our `eval` can handle definitions of `l` after macro was stated
(m '(1 2) l)
;; NIL
;; even redefining `l` works!
(setf l '((number 2) (symbol 3) (list 4)))
;; ((NUMBER 2) (SYMBOL 3) (LIST 4))
(m 1 l)
;; 2
(m '(1 2) l)
;; 4 ;; and it can handle re-definitions of `l` correctly.
;; however:
(let ((l '((number 2) (symbol 3)))) (m '(1 2) l))
;; 4 !!! this is clearly wrong! Expected is NIL!
;; so our `eval` in the macro cannot handle scoping correctly
;; which is a no-go for usage!
;; but after re-defining `l` globally to:
(setf l '((number 2) (symbol 3)))
;; ((NUMBER 2) (SYMBOL 3))
(m '(1 2) l)
;; NIL ;; it behaves correctly
(let ((lst '((number 2) (symbol 3) (list 4)))) (m '(1 2) lst))
;; *** - EVAL: variable LST has no value
;; so it becomes clear: `m` is looking in the scoping
;; where it was defined - the global scope (the parent scope of `m` when `m` was defined or within the scope of `m`).
因此,结论是:
带有eval
的给定宏工作不正常!!
因为它不能处理本地范围
因此@tfb的答案-为l
编写一个小型求值函数可能是以正确、安全、正确的方式处理此问题的唯一方法。您需要的是这样的:(defm(xl)`(case,x,@(ev