使用 (declare (type ...)) 但仍有 'safe' 功能

Using (declare (type ...)) but still have 'safe' functions

是否可以在函数中使用 (declare (type ...)) 声明,同时对函数参数执行类型检查,以便生成更快但仍然安全的代码?

例如,

(defun add (x y)
    (declare (type fixnum x y))
    (the fixnum x y))

当被称为 (add 1 "a") 时会导致未定义的行为,所以我最好将其修改为

(defun add (x y)
    (declare (type fixnum x y))
    (check-type x fixnum)
    (check-type y fixnum)
    (the fixnum x y))

但我担心编译器被允许假定 check-type 总是通过并因此省略检查。

所以我的问题是,上面的例子是否如我预期的那样错误,其次,是否有任何常用的惯用语*用于通过优化代码实现类型安全?

*) 我可以想象,例如,使用优化的 lambda 并在进行类型检查后调用它,但我想知道这是否是最优雅的方式。

您始终可以先检查类型,然后输入优化代码:

(defun foo (x)
  (check-type x fixnum)
  (locally
    (declare (fixnum x)
             (optimize (safety 0)))
    x))

LOCALLY用于局部声明。

既然你在问:

Is it possible to use (declare (type ...)) declarations in functions but also perform type-checking on the function arguments, in order to produce faster but still safe code?

在我看来,您遗漏了关于 Common Lisp 类型系统的重要一点,即该语言不是(也不可能)是静态类型的。让我们澄清一下这方面。

编程语言大致可分为三大类:

  1. 静态类型检查:每个表达式或语句都是 在编译时检查类型正确性,以便类型错误 程序开发时可以检测到,代码比较多 高效,因为必须在 运行 时间完成类型检查;
  2. 使用动态类型检查:每个操作都在 运行 次检查 为了类型正确性,所以在 运行 时间不会发生类型错误;
  3. 没有类型检查:类型错误可能发生在 运行 时间,因此 程序可以因错误或有未定义的行为而停止。

已编辑

相对于之前的分类,Common Lisp 规范将决定他们是否要遵循第二种或第三种方法的重担留给了实现!不仅如此,而且通过 optimize 声明,规范允许实现在同一程序中自由地动态更改它。

因此,大多数实现在初始优化和安全级别上都采用第二种方法,并增加了以下两种可能性:

  1. 可以请求编译器在编译某些代码时省略 运行 时间类型检查,通常是出于效率原因,因此,在该特定代码中一段代码,并且取决于优化和安全设置,语言可以表现得像第三类语言:这可以通过类型声明的提示来支持,例如变量 (declare (type fixnum x)) 和值 (the fixnum (f x));

  2. 可以在 运行-time 到 check-type 期间在代码中插入显式类型检查测试 ,因此,检查值类型的最终差异将导致“可纠正错误”。

此外,请注意,不同的编译器在编译时检查类型时可能表现不同,但它们永远不会达到具有静态类型检查的语言的编译器级别,因为 Common Lisp是一种高度动态的语言。例如考虑这个简单的案例:

(defun plus1(x) (1+ x))

(defun read-and-apply-plus1()
    (plus1 (read)))

其中无法对调用 (plus1 (read)) 进行静态类型检查,因为 (read) 的类型在编译时未知。