I think this article over-sells (to a point of being misleading) the amount of static type checking offered in Common Lisp even by a relatively good implementation like SBCL.
SBCL's static type checking provides in practice virtually zero guarantees about whether compiling your code actually implies the inexistence of type errors. In that regard, I like to consider SBCL's static type errors to be a mere courtesy [1].
Typically, the static type errors TFA demonstrates are nullified as soon as anything is abstracted away into a function. To show a trivial example:
(+ 2 "2")
may error statically because the Common Lisp implementation has special rules and knowledge about the + function, but (defun id (x) x)
(+ 2 (id "2"))
will not. Furthermore, Common Lisp has no way to label the type of the function ID to constrain its input type to be the same as its output type that works for all possible types. That is, there's nothing like the Haskell declaration id :: a -> a
id x = x
or the C++ template <typename A>
A id(A x) {
return x;
}
In Common Lisp, the closest you can get is either to declare ID to be monomorphic (i.e., just select a single type it can work with), as in (declaim (ftype (function (string) string) id))
or to say that ID can take anything as input and produce anything (possibly different from the input) as output, as in (declaim (ftype (function (t) t) id))
Here, T is not a type variable, but instead the name of the "top" type, the supertype of all types.[1] SBCL and CMUCL aren't totally random in what static type checking they perform, and the programmer can absolutely make profitable use of this checking, but you have to be quite knowledgeable of the implementation to know what exactly you can rely on.
(+ 2 (id "2")) may also produce a compile time warning. Nothing precludes from having a special rule for that. Like (+ 2 (print "2")) does warn.