【问题标题】:Checking complex types in emacs lisp?检查emacs lisp中的复杂类型?
【发布时间】:2018-03-22 14:34:05
【问题描述】:

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

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

但是,我找不到一种在不添加大量样板代码的情况下以产生清晰错误消息的方式验证即使是中等复杂类型的好方法。

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

示例

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

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

  • NUMLIST 可能不是列表(cons 或 nil)。
  • 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 定义的自定义类型来减少样板文件,这也允许递归。

然而,对于像“数字列表”这样的简单案例,无论是就代码行数而言,还是就使用该函数的程序员需要查找的文档量而言,这似乎都是大材小用。

【问题讨论】:

    标签: tree elisp typechecking


    【解决方案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-类型宏检查其类型,从而形成一个封闭的圆圈。

    【讨论】:

    • 在大多数情况下,cl-deftype 路由(我不知道)应该足够了。大多数情况都很简单,例如list-of。但是缺少文档确实一个问题,并且浪费了 Emacs Lisp(主观上)最大的优势。此外,命名空间...
    • 使用小部件,是否可以获得比“不是整数列表”更有意义的错误消息,例如您的 wrong-type argument, 3 is not a list 示例?生成指示哪里出错的错误消息是我的样板代码变得冗长的原因之一。
    • @kdb 您可以将小部件与cl-deftype 结合使用,但这(希望)只会导致诸如“foo 不是 foo 类型”之类的消息。 “3 is not a list”实际上是carcl-every中抛出的一个错误,在list-of的定义中用到。这不是你想要的那种错误,尤其是当你的数据结构在不同的地方有很多 3 时。
    • @kdb 就有意义的错误而言,如果这种帮助还不够的话,我认为您可能会错误地使用小部件(或一般的数据结构)。例如,'(1 2 3 "foo") 不是整数列表,应该与 "foo" 不是整数一样好。当然,这取决于你在哪里发现你的错误,但你可以同时拥有它。一个好的经验法则是在需要的地方断言类型并在那里抛出错误。然后您可以toggle-debug-on-error 来跟踪问题是如何出错的,同时还可以知道由于错误本身而导致错误的元素。
    猜你喜欢
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多