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