Tree 在emacs lisp中检查复杂类型?

Tree 在emacs lisp中检查复杂类型?,tree,elisp,typechecking,Tree,Elisp,Typechecking,在emacs lisp中,是否有一些无样板的方法来检查类型条件,例如带有明确错误消息的“数字列表” cl check type宏对于验证参数的类型非常有用,可以防止无效输入在子函数的某个子函数中产生类型错误时出现模糊的错误消息 但是,如果不添加大量样板代码,我就无法找到一种好方法来验证即使是中等复杂的类型,从而产生清晰的错误消息 一条清晰的错误消息应该包含参数的名称,并说明参数应该实现什么,通常是参数的值(尽管对于可能是大型树的参数来说,这实际上可能不是一个好主意) 例子 假设函数(mysum

在emacs lisp中,是否有一些无样板的方法来检查类型条件,例如带有明确错误消息的“数字列表”

cl check type
宏对于验证参数的类型非常有用,可以防止无效输入在子函数的某个子函数中产生类型错误时出现模糊的错误消息

但是,如果不添加大量样板代码,我就无法找到一种好方法来验证即使是中等复杂的类型,从而产生清晰的错误消息

一条清晰的错误消息应该包含参数的名称,并说明参数应该实现什么,通常是参数的值(尽管对于可能是大型树的参数来说,这实际上可能不是一个好主意)

例子 假设函数
(mysum NUMLIST)
需要一个数字列表作为参数。如果发出了一个
错误类型的参数
,则错误信号最好包含参数的变量名,并解释预期的内容

在这个简单的示例中,可以通过三种方式违反该条件:

  • NUMLIST不能是列表(cons或nil)
  • NUMLIST可能不是正确的列表,例如
    (12.3)
  • NUMLIST的条目不能是数字
理想情况下,所有这些都将由以下内容覆盖

;; Pseudocode, doesn't actually work
(cl-check-type numlist (listof numberp))
据我所知,
cl-check-type
宏没有一个变体允许说出“每个项都满足一个类型谓词的项列表”,因此不能直接使用它。通常我会写这样的东西

(cl-check-type numlist list)
(dolist (item list)
  (cl-check-type item numberp))
但这会产生较低的错误消息:

  • 对于NUMLIST是
    (12.3)
    ,错误消息和堆栈跟踪都不容易弄清楚,问题是由于
    NUMLIST
    引起的
  • 对于numlist为
    (1 b 3)
    ,错误消息将引用内部变量
    ,而不是指示该项来自哪个函数参数
我能想出的最好办法就是

(condition-case nil
    (dolist (item numlist)
      (cl-check-type item numberp))
  (wrong-type-argument
    (signal 'wrong-type-argument
      (list '(listof numberp) numlist 'numlist))))
使用伪谓词
(listof…
)的缺点是缺乏明确定义的含义

虽然可以定义谓词
mypackage-list-of-numbers-p
,但产生的错误

(wrong-type-argument mypackage-list-of-numbers-p SOME-VALUE-IN-VIOLATION numlist)
不会像使用预定义的谓词那样不言自明;如果
listof
是一个有效的类型谓词,
(listof numberp)
的确切含义将被认为是程序员看到错误消息时所知道的,而
mypackage-list-of-numbers-p
可能隐藏任何数量的惊喜(例如要求数字为整数),而定义
mypackage-list-of-numbers-p
仍将构成样板代码

带有“定义小部件”的递归类型 对于更复杂的结构,尤其是像任意深度树这样的递归结构,问题变得更严重。此时,可以通过定义(并对照)使用
define widget
定义的自定义类型来简化样板文件,这也允许递归


然而,对于像“数字列表”这样的简单情况,无论是在代码行方面,还是在使用该函数的程序员需要查找多少文档方面,这似乎都是一种严重的过度使用。

就示例而言,
eio
定义了
list of
,您可以将其与另一个类型一起用作
cl check type
中的参数。(如果不遵守命名约定,则会给他们加分)

对于更复杂的示例,您可能需要查看
cl deftype
,它允许您定义自己的类型。例如:

(cl-deftype nonempty-list-of (elem-type)
  `(and (list-of ,elem-type)
        (satisfies (lambda (list) (not (null list))))))
(cl-typep '(1 2 3) '(nonempty-list-of number)) ;; => t
(cl-typep nil '(nonempty-list-of number))      ;; => nil
应该注意的是,类型定义比普通谓词更难跟踪。它们作为某种符号的属性存储,缺乏任何形式的文档,甚至可能被编译。阅读该断言的人通常必须对源代码进行grep以获取其定义,如果名称还没有意义的话

如果您的数据结构非常复杂,我建议使用小部件而不是typedef,因为它们也有助于编写定制。 检查的样板相对简单:

(widget-apply (widget-convert 'mywidget) :match mydata) ;; => non-nil if mydata is valid for mywidget

当然,您可以用
cl-assert
之类的东西来包装它。如果您的结构有任何更深层次的含义,您可能还需要深入研究
eio
,这也可以在其他方面帮助您。值得注意的是,您可以使用生成的类型谓词定义类,并且所述类具有插槽,这些插槽的类型可以在实例化时通过
cl-
type宏进行检查,从而形成一个封闭的循环。

就示例而言,
eio
定义了
的列表,您可以将其与另一个类型一起用作
cl check type
中的参数。(如果不遵守命名约定,则会给他们加分)

对于更复杂的示例,您可能需要查看
cl deftype
,它允许您定义自己的类型。例如:

(cl-deftype nonempty-list-of (elem-type)
  `(and (list-of ,elem-type)
        (satisfies (lambda (list) (not (null list))))))
(cl-typep '(1 2 3) '(nonempty-list-of number)) ;; => t
(cl-typep nil '(nonempty-list-of number))      ;; => nil
应该注意的是,类型定义比普通谓词更难跟踪。它们作为某种符号的属性存储,缺乏任何形式的文档,甚至可能被编译。阅读该断言的人通常必须对源代码进行grep以获取其定义,如果名称还没有意义的话

如果您的数据结构非常复杂,我建议使用小部件而不是typedef,因为它们也有助于编写定制。 检查的样板相对简单:

(widget-apply (widget-convert 'mywidget) :match mydata) ;; => non-nil if mydata is valid for mywidget
当然,您可以用
cl-assert
之类的东西来包装它。如果您的结构有任何更深层次的含义,您可能还需要深入研究
eio
,这也可以在其他方面帮助您。值得注意的是,您可以使用生成的类型谓词定义类,以及