Python from Wise Guy's Viewpoint

Pascal Costanza costanza at web.de
Sun Oct 26 14:28:23 EST 2003


Dirk Thierbach wrote:

> Brian McNamara! <gt5163b at prism.gatech.edu> wrote:
> 
>>Pascal Costanza <costanza at web.de> once said:
>>
>>>Brian McNamara! wrote:
> 
> 
>>But I suppose you really want this example
> 
> 
>>>>(defun f (x)
>>>> (unless (< x 200)
>>>>   (cerror "Type another number"
>>>>           "You have typed a wrong number")
>>>>   (f (read)))
>>>> (* x 2))
> 
> 
>>statically typed, huh? 

Sidenote: The code above has a bug. Here is the correct version:

(defun f (x)
   (if (< x 200)
     (* x 2)
     (progn
       (cerror "Type another number"
               "You have typed a wrong number")
       (print '>)
       (f (read)))))

(Noone spotted this bug before. All transliterations I have seen so far 
have silently corrected this bug. It is interesting to note that it 
probably weren't the type systems that corrected it.)

> After quite some time, I think I have finally figured out what Pascal
> meant with
> 
> 
>>>The type system might test too many cases.
> 
> 
> I have the impression that he is confusing dynamic type errors and
> runtime errors. In Lisp and Smalltalk they are more or less the same,
> and since dynamic type errors map to static type errors, he may think
> by analogy that other runtime errors must necessarily also map to
> compile errors somehow involved with static typing. Of course this is
> nonsense; those two are completely different things. The "too many
> cases" referred to some cases of runtime errors he didn't want to be
> checked at compile time. As you cannot get "too many test cases" by
> type annotations static typing (which was the context where he made
> this comment), like you cannot get "too many test cases" by writing too
> many of them by hand, I really had a hard time figuring this out.

I think you have a restricted view of what "type" means. Here is the 
same program written in a "type-friendly" way. (Again, in standard ANSI 
Common Lisp. Note that the type is defined inside of f and not visible 
to the outside.)

(defun f (x)
   (check-type x (real * 200))
   (* x 2))

CL-USER 1 > (f 5)
10

CL-USER 2 > (f 666)

Error: The value 666 of X is not of type (REAL * 200).
   1 (continue) Supply a new value of X.
   2 (abort) Return to level 0.
   3 Return to top loop level 0.

Type :b for backtrace, :c <option number> to proceed,  or :? for other 
options

CL-USER 3 : 1 > :c 1

Enter a form to be evaluated: 66
132

>>Ok, I'll try.  If I come up short, I expect it's because I'm fluent
>>in neither Haskell nor Lisp, not because it can't be done.
> 
> 
> You don't really need runtime errors for the above example,
> but here's a similar version to yours that throws an error in
> 'cerror' to return to the toplevel. No Maybe types.
> 
> cerror :: String -> String -> IO a -> IO a
> cerror optmsg errmsg cont = do
>   print errmsg 
>   print ("1: " ++ optmsg)
>   print ("2: Fail")
>   s <- getLine
>   x <- readIO s
>   let choice 1 = cont
>       choice 2 = ioError (userError errmsg)
>   choice x
>   
> f :: Integer -> IO (Integer)
> f x =
>   if (x < 200) 
>     then return (x * 2)
>     else cerror 
>       "Type another number"
>       "You have typed a wrong number" 
>       (getLine >>= readIO >>= f)
> 
> 
>>>I don't want an "approximation of cerror". I want cerror!
> 
> 
> And you got it, exactly as you wanted. Perfectly typeable.

Nice.

> (Please don't say now "but I want to use the Lisp code, and
> it should run exactly as it is, without any changes in syntax").

No, I don't care about syntax.

You have used CPS - that's a nice solution. However, you obviously 
needed to make the type declaration for f :: Integer -> IO (Integer)

Note that my code is not restricted to integers. (But I don't know 
whether this is actually a serious restriction in Haskell. I am not a 
Haskell programmer.)

> You could even assign the very same types in Lisp if any of the 
> extensions of Lisp support that. (I didn't try.)

What you actually did is: you have assigned a "broad" type statically, 
and then you revert to a manual dynamic check to make the fine-grained 
distinction.

In both Lisp versions, you have exactly one place where you check the 
(single) type. A static type system cannot provide this kind of 
granularity. It has to make more or less distinctions.


I regard the distinction between dynamic type errors and runtime errors 
to be an artificial one, and in fact a red herring. I would rather call 
the former type _exceptions_. Exceptions are situations that occur at 
runtime and that I have a chance to control and correct in one way or 
the other. Errors are beyond control.

This might be terminological nitpicking, but I think it is a serious 
confusion, and it probably stems from weakly typed languages that allow 
for arbitrary memory access and jumps to arbitrary machine addresses. 
The niche in which such languages are still useful is getting smaller by 
the time. (Languages like Java, Python, Ruby, etc., did a fine job in 
this regard, i.e. to promote writing safe programs, rather than using 
languages for high-level purposes that are actually designed for writing 
hardware drivers.)

Anything below real runtime errors (core dumps) has the chance to be 
controlled and corrected at runtime. (Including endless loops - they 
should never be unbreakable in the first place.)

This can give you a real advantage for long-running systems or for very 
large applications that you don't want to, or cannot stop and restart on 
a regular basis.

Both (correct) code snippets I have provided are considerably smaller 
than your solution and both are more general. And my second solution 
still provides a way to correct a wrong parameter at runtime at no 
additional cost. I regard this a feature, not a problem.

Assume that you need to write a program that has only vague 
specifications and requires a high level of such flexibility in its 
specs. _I_ would fire the programmer who would insist to use a language 
that requires him to program all this flexibility by hand, and 
especially wants to see a specification for "unforeseen problems", 
whether formal or not.

See http://www.dilbert.com/comics/dilbert/archive/dilbert-20031025.html ;)


Pascal

P.S.: Please always remember the original setting of this thread. The 
original original questions was something along the lines of "why on 
earth would one not want a static type system?" I don't want to prove 
the general superiority of dynamic type checking over static type 
checking. Heck, there isn't even a general superiority of typing over 
no-typing-at-all. All these approaches have their respective advantages 
and disadvantages, and should be used in the right circumstances.

Yes, you can clearly tell from my postings that I prefer dynamic typing 
over static typing. This in turn means that I am probbaly not a good fit 
for projects that require a strong static approach. And everyone who 
prefers static typing is probably not a good fit for projects that 
require a strong dynamic approach. So what? All of us who participate in 
this discussion are probably not good fits for writing hardware drivers. 
   ;)

Everyone should do what they are best at, and everyone should use the 
tools that fit their style most.

But why on earth should anyone want to prove that their own preferred 
_personal_ style is generally superior to all others?



"If I have seen farther than others, it is because I was
standing on the shoulder of giants."

--- Isaac Newton


"In computer science, we stand on each other's feet."

--- Brian K. Reed


P.P.S.: And I still think that soft typing is the best compromise. It's 
the only approach I know of that has the potential to switch styles 
during the course without the need to completely start from scratch.





More information about the Python-list mailing list