在emacs lisp中检查复杂类型?

时间:2018-03-22 14:34:06

标签: tree elisp typechecking

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

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

但是,我找不到一种很好的方法来验证即使是中等复杂类型,也不会产生明确的错误信息,而不会添加大量的样板代码。

一个明确的错误消息应该包含参数的名称,并说明参数的期望值,通常是参数的值(尽管这对于可能是大树的参数来说实际上可能是个坏主意)。

实施例

假设函数(mysum NUMLIST)需要一个数字列表作为参数。如果信号为wrong-type-argument,则错误信号理想情况下应包含参数的变量名称,并解释预期的内容。

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

  • NUMLIST 可能不是列表(缺点或无)。
  • NUMLIST 可能不是正确的列表,例如(1 2 . 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 (1 2 . 3),错误消息和堆栈跟踪都不会很清楚,因numlist而出现问题。
  • 对于numlist为(1 b 3),错误消息将引用内部变量item,而不是指示项目来自哪个函数参数。

我能想到的最好的是

(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'的递归类型

对于更复杂的结构,尤其是诸如任意深度树之类的递归结构,问题变得更糟。此时,可以通过定义(和检查)使用define-widget定义的自定义类型来减少样板文件,这也允许递归。

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

1 个答案:

答案 0 :(得分:1)

就示例而言,eieio定义了list-of,您可以将其与cl-check-type中的其他类型一起用作参数。 (奖励指向坚持命名约定)。

(cl-typep '(1 2 3) '(list-of number))   ;; => t
(cl-typep '(1 2 . 3) '(list-of number)) ;; => wrong-type argument, 3 is not a list
(cl-typep nil '(list-of number))        ;; => t, as nil is a list
(cl-typep "foo" '(list-of number))      ;; => nil

对于更复杂的示例,您可能需要查看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或类似的内容中。如果您的结构具有与之相关的任何更深层含义,您可能还需要深入了解eieio,这也可以通过其他方式帮助您。值得注意的是,您可以使用生成的类型谓词定义类,并且所述类具有插槽,可以通过cl-类型宏在实例化时检查其类型,从而形成闭合圆。