【问题标题】:Comprehensive guide on common lisp types常见 lisp 类型的综合指南
【发布时间】:2016-10-08 21:25:12
【问题描述】:

也许这个问题太笼统了,不过我会尝试: 有没有关于 common lisp 类型的综合指南?

我对这个问题有点困惑:

为什么在make-array:element-type 中声明的非原始类型被提升为t?是否有可能对实际声明的类型进行编译时或运行时检查?

为什么 CLOS 插槽定义的类型不能作为约束,允许将任何类型的值放入插槽?再说一遍,支票呢?

函数的类型声明与declare.. 相同。它们只是对编译器的优化提示吗?

另外,我是否可以使用自定义类型说明符,包括前面提到的位置中的 satisfies 进行一些稳健检查,或者它们只能用于通过 typep 等进行显式检查?

如您所见,我脑子里有些乱,所以非常感谢任何简洁的指南(或一组指南)。

我在 SBCL,但也很高兴了解实现之间的差异。

【问题讨论】:

  • 这道题可能题太多了,有点太笼统了。但综合指南可能只是 HyperSpec 中的一部分。例如,对于类型声明,您可以查看Declaration TYPE,它准确地描述了类型声明的含义(如果变量的值实际上不是那种类型,那么您几乎有未定义的行为)。但是未定义的行为可能很有用,因为编译器可以:(i) 插入类型检查以提供安全性并让您知道是否...
  • 事情出错了;或 (ii) 使用不会检查类型的优化代码,因为您保证该值将是正确的类型。它将做什么取决于其他声明,例如任何 OPTIMIZE declarations 的值。
  • HyperSpec 的另一部分可能是“综合指南”的一部分,将是章节4. Types and Classes。该部分的内容实际上有点薄,但链接到几乎所有相关条目(例如,类型在哪里使用,可以定义它们的内容,它们的使用方式等)。
  • @JoshuaTaylor 谢谢,这不是我的,但我也很喜欢。我将链接移至答案以防 cmets 被删除。

标签: types common-lisp slots clos


【解决方案1】:

在编译期间如何处理类型由实现定义。在 SBCL 的情况下,类型通常被视为断言,但实际行为取决于优化级别。

类型作为断言意味着如果一个函数接受一个数字n 并产生一个字符串s,您通常不会假设 n 是一个数字。相反,您所拥有的是保证if 函数返回,那么n 实际上是一个数字,s 现在是一个字符串。但是如果你重用s,你的编译器就有机会跳过检查s是一个字符串。这通常是您想要的,因为您的函数在全球范围内可用,因此可以从任何地方调用。由于函数负责检查它们的输入,因此您总是首先检查 n 是一个数字是正常的。

然而,函数的类型声明可以帮助你,以防你在可以证明类型在运行时肯定会不匹配的上下文中调用函数(类型的交集为空)。为了盲目地信任类型断言,您必须降低安全级别。

注意:我最初是在评论中发布的,但为了避免它被删除,这里有一个链接到一个漂亮的图形,表示 CL 中类型之间的关系:

http://sellout.github.io/2012/03/03/common-lisp-type-hierarchy

【讨论】:

    【解决方案2】:

    为什么在 make-array 的 :element-type 中声明的非原始类型被提升为 t?是否有可能对实际声明的类型进行编译时或运行时检查?

    :element-type 参数在那里实现可以为数组选择优化的内存布局 - 主要是为了节省内存空间。这通常对原始类型有用。对于其他类型,大多数 Common Lisp 运行时将没有优化的存储实现,因此声明将没有用处。

    为什么 CLOS 插槽定义的类型不能作为约束,允许将任何类型的值放入插槽?再说一遍,支票呢?

    一个实现可以做到这一点。

    Clozure CL:

    ? (defclass foo () ((bar :type integer :initform 0 :initarg :bar)))
    #<STANDARD-CLASS FOO>
    ? (make-instance 'foo :bar "baz")
    > Error: The value "baz", derived from the initarg :BAR,
      can not be used to set the value of the slot BAR in
      #<FOO #x302000D3EC3D>, because it is not of type INTEGER. 
    

    函数的类型声明与 declare.. 相同。它们只是对编译器的优化提示吗?

    可以忽略带有 declare 的类型声明 - 例如在 Symbolics Genera 中,大多数声明将被忽略。不需要实现来处理它们。大多数实现将至少将它们解释为保证某些对象将属于该类型并为此创建优化代码 - 可能没有运行时检查和/或该类型的专用代码。但通常需要设置相应的优化级别(速度、安全性、调试...)

    另外,从 CMUCL 的编译器(SBCL,...)派生的编译器可能会使用它们进行一些编译时检查。

    但是 ANSI CL 标准中没有指定任何效果。该标准提供声明,并将解释留给实现。

    【讨论】:

      【解决方案3】:

      如果您希望编译器实际强制执行类型,则需要告诉编译器进行安全优化:

      CL-USER> (declaim (optimize (safety 3)))
      NIL
      CL-USER> (defclass foobar () ())
      #<STANDARD-CLASS COMMON-LISP-USER::FOOBAR>
      CL-USER> (defun foo (a)
                 (make-array 1 :element-type 'foobar
                               :initial-contents (list a)))
      FOO
      CL-USER> (foo (make-instance 'foobar))
      #(#<FOOBAR {1005696CE3}>)
      CL-USER> (foo 12)
      ;=> ERROR
      CL-USER> (declaim (ftype (function (integer integer) integer) quux))
      (QUUX)
      CL-USER> (defun quux (a b)
                 (+ a b))
      QUUX
      CL-USER> (quux 12 12)
      24 (5 bits, #x18, #o30, #b11000)
      CL-USER> (quux 12 "asd")
      ;=> ERROR
      

      在运行时检查类型会增加一些开销(特别是如果它发生在循环中),并且可能会针对单个值进行多次检查,因此默认情况下不检查。

      (declaim (optimize (safety 3)))
      
      (defun some-predicate-p (a)
        (format t "~&Checking type...")
        (integerp a))
      
      (deftype foo () `(satisfies some-predicate-p))
      
      (defclass bar ()
        ((foo :type foo :initarg :foo)))
      
      (declaim (ftype (function (foo) list) qwerty))
      (defun qwerty (foo)
        (loop repeat 10 collecting (make-instance 'bar :foo foo)))
      
      (qwerty 12)
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ; Checking type...
      ;=> (#<BAR {1003BCA213}> #<BAR {1003BCA263}> #<BAR {1003BCA2B3}>
      ;    #<BAR {1003BCA303}> #<BAR {1003BCA353}> #<BAR {1003BCA3A3}>
      ;    #<BAR {1003BCA3F3}> #<BAR {1003BCA443}> #<BAR {1003BCA493}>
      ;    #<BAR {1003BCA4E3}>)
      

      如果您希望函数始终检查地点的类型,而不管优化设置如何,您应该手动使用CHECK-TYPE

      【讨论】:

      • 不错!打开类型检查以进行测试/调试是一种常见的做法吗?并将其关闭以进行生产?
      • 更广泛地说:在 common lisp 中使用类型是一种普遍的做法吗?或者我应该像 JS 那样使用任何没有类型检查的语言编写代码吗?意识形态上常见的lisp方式是什么?
      • 在不太不方便的情况下使用类型声明是个好主意(尤其是对于原始类型)。在开发中你可能应该使用(declaim (optimize (debug 3) (safety 3) (speed 1))) 之类的东西,但在生产中它取决于你的需要。对于一些数学繁重的代码,您可能需要(speed 3) (safety 0),而对于正常运行时间比速度更重要的网络服务器,(safety 3) (speed 2) 加上适当的错误处理可能会更好。
      • 谢谢!你的例子在我脑海中确实让事情变得井井有条。我将不得不玩一段时间..
      • 还有一个问题:当我在 repl 中使用make-array 执行您的示例时,它工作得非常好,但是当我真正执行(make-array 1 :element-type 'foobar :initial-contents (list 1)) 时,它会创建一个没有任何抱怨的数组。我知道:在最高级别没有人这样做,但是,这样做的原因是什么?
      猜你喜欢
      • 2010-10-24
      • 1970-01-01
      • 2012-12-10
      • 2013-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多