Common Lisp 声明检查类型之间的区别
Common Lisp difference between declare check-type
有人可以解释一下来自 function:
上的 CLHS 的以下两种情况之间的区别吗(具体来说,如果我无法理解,评论在说什么)
;; This function assumes its callers have checked the types of the
;; arguments, and authorizes the compiler to build in that assumption.
(defun discriminant (a b c)
(declare (number a b c))
"Compute the discriminant for a quadratic equation."
(- (* b b) (* 4 a c))) => DISCRIMINANT
(discriminant 1 2/3 -2) => 76/9
;; This function assumes its callers have not checked the types of the
;; arguments, and performs explicit type checks before making any assumptions.
(defun careful-discriminant (a b c)
"Compute the discriminant for a quadratic equation."
(check-type a number)
(check-type b number)
(check-type c number)
(locally (declare (number a b c))
(- (* b b) (* 4 a c)))) => CAREFUL-DISCRIMINANT
(careful-discriminant 1 2/3 -2) => 76/9
宏的区别
check-type
并输入
declarations 是前者
不能被编译器忽略(并且,当检查失败时,可以
交互地纠正输入),而后者仅仅是提示
编译器(更重要的是,对于代码的读者)
可能被编译器忽略。
我正在尝试自己学习一些 CL,所以我会提供我能提供的最佳答案。 Common Lisp 是 dynamic language as compared to a static language. For a static language, check out Haskell - it does a bunch of compile time checks to ensure types match up for all functions and lets you know if it fails. However, in Common Lisp, things are a little different.
However, in Common Lisp, variables aren't typed the way they are in
languages such as Java or C++. That is, you don't need to declare the
type of object that each variable can hold. Instead, a variable can
hold values of any type and the values carry type information that can
be used to check types at runtime. Thus, Common Lisp is dynamically
typed--type errors are detected dynamically. For instance, if you pass
something other than a number to the + function, Common Lisp will
signal a type error. On the other hand, Common Lisp is a strongly
typed language in the sense that all type errors will be
detected--there's no way to treat an object as an instance of a class
that it's not.
所以我们声明为函数参数的变量默认没有类型。这对您来说可能是一本好书:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node15.html。第一段内容如下:
It is important to note that in Lisp it is data objects that are
typed, not variables. Any variable can have any Lisp object as its
value. (It is possible to make an explicit declaration that a variable
will in fact take on one of only a limited set of values. However,
such a declaration may always be omitted, and the program will still
run correctly. Such a declaration merely constitutes advice from the
user that may be useful in gaining efficiency. See declare.)
所以无论何时你创建你的函数,那些正在使用的变量都可以有任何 Lisp 对象作为它的值。
如果我们在 declare 冒险,我们会看到以下内容:
There are two distinct uses of declare , one is to declare Lisp
variables as "special" (this affects the semantics of the appropriate
bindings of the variables), and the other is to provide advice to help
the Common Lisp system (in reality the compiler) run your Lisp code
faster, or with more sophisticated debugging options.
最后,如果我们查看 check-type,我们会看到:
check-type signals a correctable error of type type-error if the
contents of place are not of the type typespec.
在 declare 和 check-type 这两种情况下,我们都给出了 Common Lisp 系统关于类型和类型检查的建议。让我们看看您提供的两个示例函数。
首先,"discriminant" 函数使用 declare 函数断言参数确实是数字,编译器不需要检查它们。 careful-discriminant函数使用check-type保证每个变量确实是一个数,然后进行运算
您可能会问 "Why should I bother with that?",答案是提供更优化的函数(判别式)或提供更好的调试和更多错误信息的函数(谨慎判别式)。为了显示差异,我启动了 SBCL 并定义了这两个函数。然后,我用反汇编来显示每个的机器代码。请注意 careful-discriminant 如何执行比 discriminant 更多的检查,从而导致更多的机器代码!
(反汇编#'判别式)
; disassembly for DISCRIMINANT
; Size: 83 bytes. Origin: #x10023700D7 ; DISCRIMINANT
; 0D7: 498B5D10 MOV RBX, [R13+16] ; thread.binding-stack-pointer
; 0DB: 48895DF8 MOV [RBP-8], RBX
; 0DF: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 0E6: 488B55E8 MOV RDX, [RBP-24]
; 0EA: 488B7DE8 MOV RDI, [RBP-24]
; 0EE: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 0F5: 488955D8 MOV [RBP-40], RDX
; 0F9: 488B55F0 MOV RDX, [RBP-16]
; 0FD: BF08000000 MOV EDI, 8
; 102: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 109: 488B7DE0 MOV RDI, [RBP-32]
; 10D: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 114: 488BFA MOV RDI, RDX
; 117: 488B55D8 MOV RDX, [RBP-40]
; 11B: FF1425B8000020 CALL QWORD PTR [#x200000B8] ; GENERIC--
; 122: 488BE5 MOV RSP, RBP
; 125: F8 CLC
; 126: 5D POP RBP
; 127: C3 RET
; 128: CC10 INT3 16 ; Invalid argument count trap
NIL
(反汇编#'小心判别式)
; disassembly for CAREFUL-DISCRIMINANT
; Size: 422 bytes. Origin: #x10023701E3 ; CAREFUL-DISCRIMINANT
; 1E3: 4D8B4510 MOV R8, [R13+16] ; thread.binding-stack-pointer
; 1E7: 4C8945F8 MOV [RBP-8], R8
; 1EB: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 1F2: EB44 JMP L1
; 1F4: 660F1F840000000000 NOP
; 1FD: 0F1F00 NOP
; 200: L0: 488B7DF0 MOV RDI, [RBP-16]
; 204: 4883EC10 SUB RSP, 16
; 208: 488B1571FFFFFF MOV RDX, [RIP-143] ; 'A
; 20F: 488B3572FFFFFF MOV RSI, [RIP-142] ; 'NUMBER
; 216: 4C894DD8 MOV [RBP-40], R9
; 21A: 488B056FFFFFFF MOV RAX, [RIP-145] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 221: B906000000 MOV ECX, 6
; 226: 48892C24 MOV [RSP], RBP
; 22A: 488BEC MOV RBP, RSP
; 22D: FF5009 CALL QWORD PTR [RAX+9]
; 230: 4C8B4DD8 MOV R9, [RBP-40]
; 234: 488955F0 MOV [RBP-16], RDX
; 238: L1: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 23F: 488B45F0 MOV RAX, [RBP-16]
; 243: 448D40F1 LEA R8D, [RAX-15]
; 247: 41F6C001 TEST R8B, 1
; 24B: 7512 JNE L2
; 24D: 4180F80A CMP R8B, 10
; 251: 740C JEQ L2
; 253: 41F6C00F TEST R8B, 15
; 257: 75A7 JNE L0
; 259: 8078F129 CMP BYTE PTR [RAX-15], 41
; 25D: 77A1 JNBE L0
; 25F: L2: EB47 JMP L4
; 261: 660F1F840000000000 NOP
; 26A: 660F1F440000 NOP
; 270: L3: 488B7DE8 MOV RDI, [RBP-24]
; 274: 4883EC10 SUB RSP, 16
; 278: 488B1519FFFFFF MOV RDX, [RIP-231] ; 'B
; 27F: 488B3502FFFFFF MOV RSI, [RIP-254] ; 'NUMBER
; 286: 4C894DD8 MOV [RBP-40], R9
; 28A: 488B05FFFEFFFF MOV RAX, [RIP-257] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 291: B906000000 MOV ECX, 6
; 296: 48892C24 MOV [RSP], RBP
; 29A: 488BEC MOV RBP, RSP
; 29D: FF5009 CALL QWORD PTR [RAX+9]
; 2A0: 4C8B4DD8 MOV R9, [RBP-40]
; 2A4: 488955E8 MOV [RBP-24], RDX
; 2A8: L4: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 2AF: 488B45E8 MOV RAX, [RBP-24]
; 2B3: 448D40F1 LEA R8D, [RAX-15]
; 2B7: 41F6C001 TEST R8B, 1
; 2BB: 7512 JNE L5
; 2BD: 4180F80A CMP R8B, 10
; 2C1: 740C JEQ L5
; 2C3: 41F6C00F TEST R8B, 15
; 2C7: 75A7 JNE L3
; 2C9: 8078F129 CMP BYTE PTR [RAX-15], 41
; 2CD: 77A1 JNBE L3
; 2CF: L5: EB3D JMP L7
; 2D1: 660F1F840000000000 NOP
; 2DA: 660F1F440000 NOP
; 2E0: L6: 498BF9 MOV RDI, R9
; 2E3: 4883EC10 SUB RSP, 16
; 2E7: 488B15B2FEFFFF MOV RDX, [RIP-334] ; 'C
; 2EE: 488B3593FEFFFF MOV RSI, [RIP-365] ; 'NUMBER
; 2F5: 488B0594FEFFFF MOV RAX, [RIP-364] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 2FC: B906000000 MOV ECX, 6
; 301: 48892C24 MOV [RSP], RBP
; 305: 488BEC MOV RBP, RSP
; 308: FF5009 CALL QWORD PTR [RAX+9]
; 30B: 4C8BCA MOV R9, RDX
; 30E: L7: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 315: 458D41F1 LEA R8D, [R9-15]
; 319: 41F6C001 TEST R8B, 1
; 31D: 7513 JNE L8
; 31F: 4180F80A CMP R8B, 10
; 323: 740D JEQ L8
; 325: 41F6C00F TEST R8B, 15
; 329: 75B5 JNE L6
; 32B: 418079F129 CMP BYTE PTR [R9-15], 41
; 330: 77AE JNBE L6
; 332: L8: 4C894DD8 MOV [RBP-40], R9
; 336: 488B55E8 MOV RDX, [RBP-24]
; 33A: 488B7DE8 MOV RDI, [RBP-24]
; 33E: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 345: 488955E0 MOV [RBP-32], RDX
; 349: 4C8B4DD8 MOV R9, [RBP-40]
; 34D: 488B55F0 MOV RDX, [RBP-16]
; 351: BF08000000 MOV EDI, 8
; 356: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 35D: 4C8B4DD8 MOV R9, [RBP-40]
; 361: 498BF9 MOV RDI, R9
; 364: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 36B: 488BFA MOV RDI, RDX
; 36E: 4C8B4DD8 MOV R9, [RBP-40]
; 372: 488B55E0 MOV RDX, [RBP-32]
; 376: FF1425B8000020 CALL QWORD PTR [#x200000B8] ; GENERIC--
; 37D: 4C8B4DD8 MOV R9, [RBP-40]
; 381: 488BE5 MOV RSP, RBP
; 384: F8 CLC
; 385: 5D POP RBP
; 386: C3 RET
; 387: CC10 INT3 16 ; Invalid argument count trap
NIL
看到这里,Common Lisp也可以编译,这让一些人感到困惑。最好在这里回答:How is Lisp dynamic and compiled?。
声明会影响编译时发生的事情。 check-type
形式是运行时间守卫。
所以,声明形式说“嘿编译器,参数 a、b、c 所持有的值只能是数字”。 check-type
形式表示“嘿函数,在执行的这一点上,检查给定值是否属于指定类型”。
CHECK-TYPE:运行时类型检查和修复
check-type
进行实际的运行时检查。它还通常提供一种交互式修复值的方法。
* (let ((a "1"))
(check-type a number)
(+ a 2))
debugger invoked on a SIMPLE-TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
The value of A is "1", which is not of type NUMBER.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [STORE-VALUE] Supply a new value for A.
1: [ABORT ] Exit debugger, returning to top level.
(SB-KERNEL:CHECK-TYPE-ERROR A "1" NUMBER NIL)
0] 0
Enter a form to be evaluated: 1
3
DECLARE : 类型声明
Common Lisp 是动态类型的:每个数据对象都有一个类型。
Common Lisp 还允许对变量和函数使用静态类型。有
- 各种类型和复合类型
- 使用
deftype
定义新类型的方法
- 类型声明
declare
- 子类型检查
subtypep
- 运行时类型检查
typep
typecase
、ctypecase
和 etypecase
条件运行时类型
现在 Common Lisp 实现对各种事物使用类型声明,并且它们对它们所做的事情是高度特定于实现的。
Common Lisp编译器中(declare (type ...))
静态类型声明的主要用法是:
无视他们。通常 Lisp 解释器 和一些 编译器 完全忽略它们
将它们用于速度和 space 优化。这是由许多编译器完成的。他们可以使用这些类型声明来创建专门的代码。
将它们用于运行时类型检查。一些实现使用类型声明进行运行时检查。
将它们用于编译时类型检查。一些实现使用类型声明进行编译时类型检查。例如 sbcl 和 cmucl。
请注意,Common Lisp 标准并未说明如何使用这些类型声明。它只是提供了一种语法来定义和声明类型。 Common Lisp 实现要么使用它们,要么忽略它们。
在 SBCL 和 CMUCL 中可以找到类型声明的特别复杂的用法。
类型检查示例
让我们看看 SBCL 如何使用类型声明进行运行时和编译时类型检查:
使用 SBCL 进行运行时类型检查:
* (defun add (a b)
(declare (type number a b))
(list a b))
ADD
* (add 1 "3")
debugger invoked on a TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
The value
"3"
is not of type
NUMBER
when binding B
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(ADD 1 "3") [external]
source: (SB-INT:NAMED-LAMBDA ADD
(A B)
(DECLARE (TYPE NUMBER A B))
(BLOCK ADD (LIST A B)))
0]
正如我们所见,SBCL 使用类型声明进行运行时检查。但不同于check-type
它不提供提供不同的值和相应的重新检查...
使用 SBCL 进行编译时类型检查:
* (defun subtract (a b)
(declare (type number a b))
(concatenate 'string a "-" b " is " (- a b)))
; in: DEFUN SUBTRACT
; (CONCATENATE 'STRING A "-" B " is " (- A B))
;
; caught WARNING:
; Derived type of (SB-KERNEL:SYMEVAL 'A) is
; (VALUES NUMBER &OPTIONAL),
; conflicting with its asserted type
; SEQUENCE.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
SUBTRACT
如您所见,我们正在尝试使用数字作为序列。 SBCL 在编译时检测到并发出警告。
有人可以解释一下来自 function:
上的 CLHS 的以下两种情况之间的区别吗(具体来说,如果我无法理解,评论在说什么);; This function assumes its callers have checked the types of the
;; arguments, and authorizes the compiler to build in that assumption.
(defun discriminant (a b c)
(declare (number a b c))
"Compute the discriminant for a quadratic equation."
(- (* b b) (* 4 a c))) => DISCRIMINANT
(discriminant 1 2/3 -2) => 76/9
;; This function assumes its callers have not checked the types of the
;; arguments, and performs explicit type checks before making any assumptions.
(defun careful-discriminant (a b c)
"Compute the discriminant for a quadratic equation."
(check-type a number)
(check-type b number)
(check-type c number)
(locally (declare (number a b c))
(- (* b b) (* 4 a c)))) => CAREFUL-DISCRIMINANT
(careful-discriminant 1 2/3 -2) => 76/9
宏的区别
check-type
并输入
declarations 是前者
不能被编译器忽略(并且,当检查失败时,可以
交互地纠正输入),而后者仅仅是提示
编译器(更重要的是,对于代码的读者)
可能被编译器忽略。
我正在尝试自己学习一些 CL,所以我会提供我能提供的最佳答案。 Common Lisp 是 dynamic language as compared to a static language. For a static language, check out Haskell - it does a bunch of compile time checks to ensure types match up for all functions and lets you know if it fails. However, in Common Lisp, things are a little different.
However, in Common Lisp, variables aren't typed the way they are in languages such as Java or C++. That is, you don't need to declare the type of object that each variable can hold. Instead, a variable can hold values of any type and the values carry type information that can be used to check types at runtime. Thus, Common Lisp is dynamically typed--type errors are detected dynamically. For instance, if you pass something other than a number to the + function, Common Lisp will signal a type error. On the other hand, Common Lisp is a strongly typed language in the sense that all type errors will be detected--there's no way to treat an object as an instance of a class that it's not.
所以我们声明为函数参数的变量默认没有类型。这对您来说可能是一本好书:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node15.html。第一段内容如下:
It is important to note that in Lisp it is data objects that are typed, not variables. Any variable can have any Lisp object as its value. (It is possible to make an explicit declaration that a variable will in fact take on one of only a limited set of values. However, such a declaration may always be omitted, and the program will still run correctly. Such a declaration merely constitutes advice from the user that may be useful in gaining efficiency. See declare.)
所以无论何时你创建你的函数,那些正在使用的变量都可以有任何 Lisp 对象作为它的值。
如果我们在 declare 冒险,我们会看到以下内容:
There are two distinct uses of declare , one is to declare Lisp variables as "special" (this affects the semantics of the appropriate bindings of the variables), and the other is to provide advice to help the Common Lisp system (in reality the compiler) run your Lisp code faster, or with more sophisticated debugging options.
最后,如果我们查看 check-type,我们会看到:
check-type signals a correctable error of type type-error if the contents of place are not of the type typespec.
在 declare 和 check-type 这两种情况下,我们都给出了 Common Lisp 系统关于类型和类型检查的建议。让我们看看您提供的两个示例函数。
首先,"discriminant" 函数使用 declare 函数断言参数确实是数字,编译器不需要检查它们。 careful-discriminant函数使用check-type保证每个变量确实是一个数,然后进行运算
您可能会问 "Why should I bother with that?",答案是提供更优化的函数(判别式)或提供更好的调试和更多错误信息的函数(谨慎判别式)。为了显示差异,我启动了 SBCL 并定义了这两个函数。然后,我用反汇编来显示每个的机器代码。请注意 careful-discriminant 如何执行比 discriminant 更多的检查,从而导致更多的机器代码!
(反汇编#'判别式)
; disassembly for DISCRIMINANT
; Size: 83 bytes. Origin: #x10023700D7 ; DISCRIMINANT
; 0D7: 498B5D10 MOV RBX, [R13+16] ; thread.binding-stack-pointer
; 0DB: 48895DF8 MOV [RBP-8], RBX
; 0DF: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 0E6: 488B55E8 MOV RDX, [RBP-24]
; 0EA: 488B7DE8 MOV RDI, [RBP-24]
; 0EE: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 0F5: 488955D8 MOV [RBP-40], RDX
; 0F9: 488B55F0 MOV RDX, [RBP-16]
; 0FD: BF08000000 MOV EDI, 8
; 102: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 109: 488B7DE0 MOV RDI, [RBP-32]
; 10D: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 114: 488BFA MOV RDI, RDX
; 117: 488B55D8 MOV RDX, [RBP-40]
; 11B: FF1425B8000020 CALL QWORD PTR [#x200000B8] ; GENERIC--
; 122: 488BE5 MOV RSP, RBP
; 125: F8 CLC
; 126: 5D POP RBP
; 127: C3 RET
; 128: CC10 INT3 16 ; Invalid argument count trap
NIL
(反汇编#'小心判别式)
; disassembly for CAREFUL-DISCRIMINANT
; Size: 422 bytes. Origin: #x10023701E3 ; CAREFUL-DISCRIMINANT
; 1E3: 4D8B4510 MOV R8, [R13+16] ; thread.binding-stack-pointer
; 1E7: 4C8945F8 MOV [RBP-8], R8
; 1EB: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 1F2: EB44 JMP L1
; 1F4: 660F1F840000000000 NOP
; 1FD: 0F1F00 NOP
; 200: L0: 488B7DF0 MOV RDI, [RBP-16]
; 204: 4883EC10 SUB RSP, 16
; 208: 488B1571FFFFFF MOV RDX, [RIP-143] ; 'A
; 20F: 488B3572FFFFFF MOV RSI, [RIP-142] ; 'NUMBER
; 216: 4C894DD8 MOV [RBP-40], R9
; 21A: 488B056FFFFFFF MOV RAX, [RIP-145] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 221: B906000000 MOV ECX, 6
; 226: 48892C24 MOV [RSP], RBP
; 22A: 488BEC MOV RBP, RSP
; 22D: FF5009 CALL QWORD PTR [RAX+9]
; 230: 4C8B4DD8 MOV R9, [RBP-40]
; 234: 488955F0 MOV [RBP-16], RDX
; 238: L1: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 23F: 488B45F0 MOV RAX, [RBP-16]
; 243: 448D40F1 LEA R8D, [RAX-15]
; 247: 41F6C001 TEST R8B, 1
; 24B: 7512 JNE L2
; 24D: 4180F80A CMP R8B, 10
; 251: 740C JEQ L2
; 253: 41F6C00F TEST R8B, 15
; 257: 75A7 JNE L0
; 259: 8078F129 CMP BYTE PTR [RAX-15], 41
; 25D: 77A1 JNBE L0
; 25F: L2: EB47 JMP L4
; 261: 660F1F840000000000 NOP
; 26A: 660F1F440000 NOP
; 270: L3: 488B7DE8 MOV RDI, [RBP-24]
; 274: 4883EC10 SUB RSP, 16
; 278: 488B1519FFFFFF MOV RDX, [RIP-231] ; 'B
; 27F: 488B3502FFFFFF MOV RSI, [RIP-254] ; 'NUMBER
; 286: 4C894DD8 MOV [RBP-40], R9
; 28A: 488B05FFFEFFFF MOV RAX, [RIP-257] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 291: B906000000 MOV ECX, 6
; 296: 48892C24 MOV [RSP], RBP
; 29A: 488BEC MOV RBP, RSP
; 29D: FF5009 CALL QWORD PTR [RAX+9]
; 2A0: 4C8B4DD8 MOV R9, [RBP-40]
; 2A4: 488955E8 MOV [RBP-24], RDX
; 2A8: L4: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 2AF: 488B45E8 MOV RAX, [RBP-24]
; 2B3: 448D40F1 LEA R8D, [RAX-15]
; 2B7: 41F6C001 TEST R8B, 1
; 2BB: 7512 JNE L5
; 2BD: 4180F80A CMP R8B, 10
; 2C1: 740C JEQ L5
; 2C3: 41F6C00F TEST R8B, 15
; 2C7: 75A7 JNE L3
; 2C9: 8078F129 CMP BYTE PTR [RAX-15], 41
; 2CD: 77A1 JNBE L3
; 2CF: L5: EB3D JMP L7
; 2D1: 660F1F840000000000 NOP
; 2DA: 660F1F440000 NOP
; 2E0: L6: 498BF9 MOV RDI, R9
; 2E3: 4883EC10 SUB RSP, 16
; 2E7: 488B15B2FEFFFF MOV RDX, [RIP-334] ; 'C
; 2EE: 488B3593FEFFFF MOV RSI, [RIP-365] ; 'NUMBER
; 2F5: 488B0594FEFFFF MOV RAX, [RIP-364] ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 2FC: B906000000 MOV ECX, 6
; 301: 48892C24 MOV [RSP], RBP
; 305: 488BEC MOV RBP, RSP
; 308: FF5009 CALL QWORD PTR [RAX+9]
; 30B: 4C8BCA MOV R9, RDX
; 30E: L7: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; 315: 458D41F1 LEA R8D, [R9-15]
; 319: 41F6C001 TEST R8B, 1
; 31D: 7513 JNE L8
; 31F: 4180F80A CMP R8B, 10
; 323: 740D JEQ L8
; 325: 41F6C00F TEST R8B, 15
; 329: 75B5 JNE L6
; 32B: 418079F129 CMP BYTE PTR [R9-15], 41
; 330: 77AE JNBE L6
; 332: L8: 4C894DD8 MOV [RBP-40], R9
; 336: 488B55E8 MOV RDX, [RBP-24]
; 33A: 488B7DE8 MOV RDI, [RBP-24]
; 33E: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 345: 488955E0 MOV [RBP-32], RDX
; 349: 4C8B4DD8 MOV R9, [RBP-40]
; 34D: 488B55F0 MOV RDX, [RBP-16]
; 351: BF08000000 MOV EDI, 8
; 356: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 35D: 4C8B4DD8 MOV R9, [RBP-40]
; 361: 498BF9 MOV RDI, R9
; 364: FF1425C0000020 CALL QWORD PTR [#x200000C0] ; GENERIC-*
; 36B: 488BFA MOV RDI, RDX
; 36E: 4C8B4DD8 MOV R9, [RBP-40]
; 372: 488B55E0 MOV RDX, [RBP-32]
; 376: FF1425B8000020 CALL QWORD PTR [#x200000B8] ; GENERIC--
; 37D: 4C8B4DD8 MOV R9, [RBP-40]
; 381: 488BE5 MOV RSP, RBP
; 384: F8 CLC
; 385: 5D POP RBP
; 386: C3 RET
; 387: CC10 INT3 16 ; Invalid argument count trap
NIL
看到这里,Common Lisp也可以编译,这让一些人感到困惑。最好在这里回答:How is Lisp dynamic and compiled?。
声明会影响编译时发生的事情。 check-type
形式是运行时间守卫。
所以,声明形式说“嘿编译器,参数 a、b、c 所持有的值只能是数字”。 check-type
形式表示“嘿函数,在执行的这一点上,检查给定值是否属于指定类型”。
CHECK-TYPE:运行时类型检查和修复
check-type
进行实际的运行时检查。它还通常提供一种交互式修复值的方法。
* (let ((a "1"))
(check-type a number)
(+ a 2))
debugger invoked on a SIMPLE-TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
The value of A is "1", which is not of type NUMBER.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [STORE-VALUE] Supply a new value for A.
1: [ABORT ] Exit debugger, returning to top level.
(SB-KERNEL:CHECK-TYPE-ERROR A "1" NUMBER NIL)
0] 0
Enter a form to be evaluated: 1
3
DECLARE : 类型声明
Common Lisp 是动态类型的:每个数据对象都有一个类型。
Common Lisp 还允许对变量和函数使用静态类型。有
- 各种类型和复合类型
- 使用
deftype
定义新类型的方法
- 类型声明
declare
- 子类型检查
subtypep
- 运行时类型检查
typep
typecase
、ctypecase
和etypecase
条件运行时类型
现在 Common Lisp 实现对各种事物使用类型声明,并且它们对它们所做的事情是高度特定于实现的。
Common Lisp编译器中(declare (type ...))
静态类型声明的主要用法是:
无视他们。通常 Lisp 解释器 和一些 编译器 完全忽略它们
将它们用于速度和 space 优化。这是由许多编译器完成的。他们可以使用这些类型声明来创建专门的代码。
将它们用于运行时类型检查。一些实现使用类型声明进行运行时检查。
将它们用于编译时类型检查。一些实现使用类型声明进行编译时类型检查。例如 sbcl 和 cmucl。
请注意,Common Lisp 标准并未说明如何使用这些类型声明。它只是提供了一种语法来定义和声明类型。 Common Lisp 实现要么使用它们,要么忽略它们。
在 SBCL 和 CMUCL 中可以找到类型声明的特别复杂的用法。
类型检查示例
让我们看看 SBCL 如何使用类型声明进行运行时和编译时类型检查:
使用 SBCL 进行运行时类型检查:
* (defun add (a b)
(declare (type number a b))
(list a b))
ADD
* (add 1 "3")
debugger invoked on a TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
The value
"3"
is not of type
NUMBER
when binding B
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(ADD 1 "3") [external]
source: (SB-INT:NAMED-LAMBDA ADD
(A B)
(DECLARE (TYPE NUMBER A B))
(BLOCK ADD (LIST A B)))
0]
正如我们所见,SBCL 使用类型声明进行运行时检查。但不同于check-type
它不提供提供不同的值和相应的重新检查...
使用 SBCL 进行编译时类型检查:
* (defun subtract (a b)
(declare (type number a b))
(concatenate 'string a "-" b " is " (- a b)))
; in: DEFUN SUBTRACT
; (CONCATENATE 'STRING A "-" B " is " (- A B))
;
; caught WARNING:
; Derived type of (SB-KERNEL:SYMEVAL 'A) is
; (VALUES NUMBER &OPTIONAL),
; conflicting with its asserted type
; SEQUENCE.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
SUBTRACT
如您所见,我们正在尝试使用数字作为序列。 SBCL 在编译时检测到并发出警告。