Next: Compiler Policy, Previous: Types in Python, Up: The Compiler [Contents][Index]
Since Python does much more comprehensive type checking than other Lisp compilers, Python will detect type errors in many programs that have been debugged using other compilers. These errors are mostly incorrect declarations, although compile-time type errors can find actual bugs if parts of the program have never been tested.
Some incorrect declarations can only be detected by run-time type checking. It is very important to initially compile programs with full type checks and then test this version. After the checking version has been tested, then you can consider weakening or eliminating type checks. This applies even to previously debugged programs. Python does much more type inference than other Common Lisp compilers, so believing an incorrect declaration does much more damage.
The most common problem is with variables whose initial value doesn’t match the type declaration. Incorrect initial values will always be flagged by a compile-time type error, and they are simple to fix once located. Consider this code fragment:
(prog (foo) (declare (fixnum foo)) (setq foo ...) ...)
Here the variable foo
is given an initial value of nil
, but
is declared to be a fixnum
. Even if it is never read, the
initial value of a variable must match the declared type. There are
two ways to fix this problem. Change the declaration:
(prog (foo) (declare (type (or fixnum null) foo)) (setq foo ...) ...)
or change the initial value:
(prog ((foo 0)) (declare (fixnum foo)) (setq foo ...) ...)
It is generally preferable to change to a legal initial value rather than to weaken the declaration, but sometimes it is simpler to weaken the declaration than to try to make an initial value of the appropriate type.
Another declaration problem occasionally encountered is incorrect
declarations on defmacro
arguments. This probably usually
happens when a function is converted into a macro. Consider this
macro:
(defmacro my-1+ (x) (declare (fixnum x)) `(the fixnum (1+ ,x)))
Although legal and well-defined Common Lisp, this meaning of this definition is almost certainly not what the writer intended. For example, this call is illegal:
(my-1+ (+ 4 5))
The call is illegal because the argument to the macro is
(+ 4 5)
, which is a list
, not a fixnum
. Because of
macro semantics, it is hardly ever useful to declare the types of
macro arguments. If you really want to assert something about the
type of the result of evaluating a macro argument, then put a
the
in the expansion:
(defmacro my-1+ (x) `(the fixnum (1+ (the fixnum ,x))))
In this case, it would be stylistically preferable to change this macro back to a function and declare it inline. Macros have no efficiency advantage over inline functions when using Python. See inline-expansion.
Some more subtle problems are caused by incorrect declarations that can’t be detected at compile time. Consider this code:
(do ((pos 0 (position #\a string :start (1+ pos)))) ((null pos)) (declare (fixnum pos)) ...)
Although pos
is almost always a fixnum
, it is nil
at the end of the loop. If this example is compiled with full type
checks (the default), then running it will signal a type error at the
end of the loop. If compiled without type checks, the program will go
into an infinite loop (or perhaps position
will complain
because (1+ nil)
isn’t a sensible start.) Why? Because if
you compile without type checks, the compiler just quietly believes
the type declaration. Since pos
is always a fixnum
, it
is never nil
, so (null pos)
is never true, and the loop
exit test is optimized away. Such errors are sometimes flagged by
unreachable code notes (see dead-code-notes), but it is still
important to initially compile any system with full type checks, even
if the system works fine when compiled using other compilers.
In this case, the fix is to weaken the type declaration to
(or fixnum null)
.8
Note that there is usually little performance penalty for weakening a
declaration in this way. Any numeric operations in the body can still
assume the variable is a fixnum
, since nil
is not a legal
numeric argument. Another possible fix would be to say:
(do ((pos 0 (position #\a string :start (1+ pos)))) ((null pos)) (let ((pos pos)) (declare (fixnum pos)) ...))
This would be preferable in some circumstances, since it would allow a
non-standard representation to be used for the local pos
variable in the loop body (see section ND-variables.)
In summary, remember that all values that a variable ever has must be of the declared type, and that you should test using safe code initially.
Actually, this declaration is
totally unnecessary in Python, since it already knows
position
returns a non-negative fixnum
or nil
.
Next: Compiler Policy, Previous: Types in Python, Up: The Compiler [Contents][Index]