Next: , Previous: , Up: The Compiler   [Contents][Index]


4.6 Getting Existing Programs to Run

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.


Footnotes

(8)

Actually, this declaration is totally unnecessary in Python, since it already knows position returns a non-negative fixnum or nil.


Next: , Previous: , Up: The Compiler   [Contents][Index]