Common Lisp:是否有带有类型说明符的“标签”版本?
Common Lisp: Is there a version of `labels` with a type specifier?
是否有一个 Common Lisp 结构对于 labels
就像 defmethod
对于 defun
?也就是说,我想使用 labels
(或类似的东西)来定义几个具有相同名称但接受的参数不同的局部函数,让编译器在它们之间进行选择。
作为MWE,我想实现以下功能
(defmethod write-first-item-type ((lst list))
"Writes the type of the first item in lst."
(labels ((write-single ()
(format t "a single float"))
(write-double ()
(format t "a double float")))
(format t "The first item is: ~A.~%"
(cond ((eql (type-of (car lst)) 'single-float)
(write-single))
((eql (type-of (car lst)) 'double-float)
(write-double))
(t
(format t "unknown"))))))
类似
(defmethod write-first-item-type ((lst list))
"Should write the type of the first item in lst but does not compile."
(label-method ((write-type ((item single-float))
(format t "a single float"))
(write-type ((ifem double-float))
(format t "a double float")))
(format t "The first item is: ~A.~%"
(write-type (car lst)))))
诚然,我的 MWE 相当愚蠢。我的实际动机是,在清理我的源代码时,我想将一堆小辅助函数(使用 defmethod
创建)放入使用它们的一个大函数中。也请随意评论这个动机!
我不知道有那种内置功能。但是,只要不需要 OOP 的其他部分(即继承和其他调度命令),这种功能并不难从头开始构建。
如果你永远不会在你的 method-labels
代码中调用 (call-next-method)
(你不会在 labels
代码中调用,这只是定义一个调度的问题 table 并相应地分派“方法”。要分解它,它应该是一个本地宏:
- 定义一个局部变量(
gensym
)作为dispatchtable;
- 将函数作为闭包与其专门的 lambda 列表一起注册到调度中 table;
- 注册一个本地“通用”函数,调用时根据提供的参数从调度table中找到要调用的函数。
要找到函数,您可能需要也可能不需要:
- 对调度进行排序table(但如果没有
eql
说明符也没有继承,这可以避免)
- 编写专用代码来匹配
&optional
、&key
和 lambda 列表的其他选项的专用参数(或者您可以使用 destructuring-bind
,但您需要将专门的 lambda 列表转换为 lambda 列表)- 可能有相关工具,但我不知道。
在最简单的情况下,lambda 列表中的参数数量是固定的,调度可以像几个 (e)typecase
.
一样简单
local/global 方法对您来说可能是命名空间的问题。
你不想用这件事污染当前的命名空间。
如何创建即时小型 namespace/packages 并在其中使用 CLOS 的“全局”方法?
具有对其他函数“不可见”的局部函数几乎具有相同的效果。
(defpackage my-sub
(:use :cl)
(:export #:write-type))
(in-package :my-sub)
(defgeneric write-type (x)
(:documentation "write type of x"))
(defmethod write-type ((x float))
(typecase x
(single-float "single float")
(double-float "double float")))
(defmethod write-type ((x string))
"string")
(defpackage my-main
(:use :cl
:my-sub))
(in-package :my-main)
(defmethod write-first-item-type ((lst list))
(format nil "first item is: ~A." (my-sub:write-type (car lst))))
(write-first-item-type '("a" b c))
;; "first item is: string."
(write-first-item-type '(1.0 2 3))
;; "first item is: single float."
(write-first-item-type '(1.0d0 2 3))
;; "first item is: double float."
defmethod
无论如何只能为 classes
发送 - 内置或自制。
但是您想派遣 types
.
所以我为 class float
的成员(single-float
和 double-float
)举了例子 - 使用 typecase
手动派遣他们。
和内置 class string
.
使用包装进行命名空间分离。
不过,在这种情况下,手动派送会更好。
(defmethod write-first-item-type ((lst list))
(labels ((write-type (x)
(typecase x
(single-float "single float")
(double-float "double float")
(string "string")
(t "unkown"))))
(format nil "The first item is: ~A." (write-type (car lst)))))
在此处查看为什么从 CLOS 中删除了本地通用绑定的初始提案:
是否有一个 Common Lisp 结构对于 labels
就像 defmethod
对于 defun
?也就是说,我想使用 labels
(或类似的东西)来定义几个具有相同名称但接受的参数不同的局部函数,让编译器在它们之间进行选择。
作为MWE,我想实现以下功能
(defmethod write-first-item-type ((lst list))
"Writes the type of the first item in lst."
(labels ((write-single ()
(format t "a single float"))
(write-double ()
(format t "a double float")))
(format t "The first item is: ~A.~%"
(cond ((eql (type-of (car lst)) 'single-float)
(write-single))
((eql (type-of (car lst)) 'double-float)
(write-double))
(t
(format t "unknown"))))))
类似
(defmethod write-first-item-type ((lst list))
"Should write the type of the first item in lst but does not compile."
(label-method ((write-type ((item single-float))
(format t "a single float"))
(write-type ((ifem double-float))
(format t "a double float")))
(format t "The first item is: ~A.~%"
(write-type (car lst)))))
诚然,我的 MWE 相当愚蠢。我的实际动机是,在清理我的源代码时,我想将一堆小辅助函数(使用 defmethod
创建)放入使用它们的一个大函数中。也请随意评论这个动机!
我不知道有那种内置功能。但是,只要不需要 OOP 的其他部分(即继承和其他调度命令),这种功能并不难从头开始构建。
如果你永远不会在你的 method-labels
代码中调用 (call-next-method)
(你不会在 labels
代码中调用,这只是定义一个调度的问题 table 并相应地分派“方法”。要分解它,它应该是一个本地宏:
- 定义一个局部变量(
gensym
)作为dispatchtable; - 将函数作为闭包与其专门的 lambda 列表一起注册到调度中 table;
- 注册一个本地“通用”函数,调用时根据提供的参数从调度table中找到要调用的函数。
要找到函数,您可能需要也可能不需要:
- 对调度进行排序table(但如果没有
eql
说明符也没有继承,这可以避免) - 编写专用代码来匹配
&optional
、&key
和 lambda 列表的其他选项的专用参数(或者您可以使用destructuring-bind
,但您需要将专门的 lambda 列表转换为 lambda 列表)- 可能有相关工具,但我不知道。
在最简单的情况下,lambda 列表中的参数数量是固定的,调度可以像几个 (e)typecase
.
local/global 方法对您来说可能是命名空间的问题。 你不想用这件事污染当前的命名空间。
如何创建即时小型 namespace/packages 并在其中使用 CLOS 的“全局”方法?
具有对其他函数“不可见”的局部函数几乎具有相同的效果。
(defpackage my-sub
(:use :cl)
(:export #:write-type))
(in-package :my-sub)
(defgeneric write-type (x)
(:documentation "write type of x"))
(defmethod write-type ((x float))
(typecase x
(single-float "single float")
(double-float "double float")))
(defmethod write-type ((x string))
"string")
(defpackage my-main
(:use :cl
:my-sub))
(in-package :my-main)
(defmethod write-first-item-type ((lst list))
(format nil "first item is: ~A." (my-sub:write-type (car lst))))
(write-first-item-type '("a" b c))
;; "first item is: string."
(write-first-item-type '(1.0 2 3))
;; "first item is: single float."
(write-first-item-type '(1.0d0 2 3))
;; "first item is: double float."
defmethod
无论如何只能为 classes
发送 - 内置或自制。
但是您想派遣 types
.
所以我为 class float
的成员(single-float
和 double-float
)举了例子 - 使用 typecase
手动派遣他们。
和内置 class string
.
使用包装进行命名空间分离。
不过,在这种情况下,手动派送会更好。
(defmethod write-first-item-type ((lst list))
(labels ((write-type (x)
(typecase x
(single-float "single float")
(double-float "double float")
(string "string")
(t "unkown"))))
(format nil "The first item is: ~A." (write-type (car lst)))))
在此处查看为什么从 CLOS 中删除了本地通用绑定的初始提案: