也许这个问题太笼统了,不过我会尝试: 常见的lisp中是否有关于类型的综合指南?
我对此话题感到困惑:
为什么在make-array
' :element-type
中声明的非原始类型会被提升为t
?是否有可能对真实声明的类型进行编译时或运行时检查?
为什么CLOS插槽定义的类型不能作为约束,允许将任何类型的值放入插槽?再次,检查呢?
功能相同'使用declare
的类型声明..它们只是编译器的优化提示吗?
另外,我可以在前面提到的地方使用自定义类型说明符(包括satisfies
进行一些强大的检查),还是只能用于typep
e.t.c的显式检查?
正如你所看到的,我头脑中有些混乱,所以非常感谢任何整洁的指南(或一套指南)。
我在SBCL,但也很高兴知道实施之间的差异。
答案 0 :(得分:4)
如果您希望编译器实际强制执行类型,则需要告诉编译器优化安全性:
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
。
答案 1 :(得分:3)
为什么在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标准中没有指定任何效果。该标准提供了声明并将解释留给了实现。
答案 2 :(得分:2)
编译期间如何处理类型由实现定义。在SBCL的情况下,类型通常被视为断言,但实际行为取决于优化级别。
类型作为断言意味着如果函数采用数字n
并生成字符串s
,则通常不会假设 n
是数。相反,你所拥有的是保证 如果函数返回,那么n
实际上是一个数字,s
现在是一个字符串。但是如果重用s
,编译器就有机会跳过检查s
是否为字符串。这通常是您想要的,因为您的功能在全球范围内可用,因此可以从任何地方调用。由于函数负责检查其输入,因此您始终首先检查n
是否为数字是正常的。
然而,函数的类型声明可以帮助您,以防您在上下文中调用函数,在该上下文中可以证明类型在运行时肯定会不匹配(类型的交集是空的)。为了盲目地信任类型断言,你必须降低安全级别。
注意:我最初是在评论中发布的,但是为了避免被删除,这里有一个指向CL中类型之间关系的漂亮图形的链接:
https://sellout.github.io/2012/03/03/common-lisp-type-hierarchy/