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 并相应地分派“方法”。要分解它,它应该是一个本地宏:

  1. 定义一个局部变量(gensym)作为dispatchtable;
  2. 将函数作为闭包与其专门的 lambda 列表一起注册到调度中 table;
  3. 注册一个本地“通用”函数,调用时根据提供的参数从调度table中找到要调用的函数。

要找到函数,您可能需要也可能不需要:

  1. 对调度进行排序table(但如果没有eql说明符也没有继承,这可以避免)
  2. 编写专用代码来匹配 &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-floatdouble-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 中删除了本地通用绑定的初始提案:

Issue GENERIC-FLET-POORLY-DESIGNED Writeup