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
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
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)
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.