Python from Wise Guy's Viewpoint

D

Dirk Thierbach

Pascal Costanza said:
You need some testing discipline, which is supported well by unit
testing frameworks.

IMHO it helps to think about static typing as a special kind of unit
tests. Like unit tests, they verify that for some input values, the
function in question will produce the correct output values. Unlike
unit tests, they do this for a class of values, instead of testing
statistically by example. And unlike unit tests, they are pervasive:
Every execution path will be automatically tested; you don't have
to invest brain power to make sure you don't forget one.

Type inference will automatically write unit tests for you (besides
other uses like hinting that a routine may be more general than you
thought). But since the computer is not very smart, they will test
only more or less trivial things. But that's still good, because then
you don't have to write the trivial unit tests, and only have to care
about the non-trivial ones.

Type annotations are an assertion language that you use to write down
that kind of unit tests.
Static type systems are claimed to generally improve your code. I
don't see that.

They do it for the same reason that unit tests do:

* They are executable documention.

* By writing them down first, you focus on what you want to do.

* They help with refactoring.

etc.

Of course you can replace the benefits of static typing by enough unit
tests. But they are different verification tools: For some kind of
problems, one is better, for other kinds, the other. There's no reason
not to use both.

- Dirk
 
P

Pascal Costanza

Marshall said:
Provided you think to write such a test, and expend the effort
to do so. Contrast to what happens in a statically typed language,
where this is done for you automatically.

There are other things that are done automatically for me in dynamically
typed languages that I care more about than such static checks. I don't
recall ever writing 1 + "bar". (Yes, this is a rhetorical statement. ;)
Unit tests are great; I heartily endorse them. But they *cannot*
do everything that static type checking can do. Likewise,
static type checking *cannot* do everything unit testing
can do.
Right.

So again I ask, why is it either/or? Why not both? I've had
*great* success building systems with comprehensive unit
test suites in statically typed languages. The unit tests catch
some bugs, and the static type checking catches other bugs.

That's great for you, and if it works for you, just keep it up.

But I have given reasons why one would not want to have static type
checking by default. All I am trying to say is that this depends on the
context. Static type systems are definitely not _generally_ better than
dynamic type systems.


Pascal
 
R

Ralph Becket

Pascal Costanza said:
+ Design process: There are clear indications that processes like
extreme programming work better than processes that require some kind of
specification stage. Dynamic typing works better with XP than static
typing because with dynamic typing you can write unit tests without
having the need to immediately write appropriate target code.

This is utterly bogus. If you write unit tests beforehand, you are
already pre-specifying the interface that the code to be tested will
present.

I fail to see how dynamic typing can confer any kind of advantage here.
+ Documentation: Comments are usually better for handling documentation.
;) If you want your "comments" checked, you can add assertions.

Are you seriously claiming that concise, *automatically checked*
documentation (which is one function served by explicit type
declarations) is inferior to unchecked, ad hoc commenting?

For one thing, type declarations *cannot* become out-of-date (as
comments can and often do) because a discrepancy between type
declaration and definition will be immidiately flagged by the compiler.
+ Error checking: I can only guess what you mean by this. If you mean
something like Java's checked exceptions, there are clear signs that
this is a very bad feature.

I think Fergus was referring to static error checking, but (and forgive
me if I'm wrong here) that's a feature you seem to insist has little or
no practical value - indeed, you seem to claim it is even an impediment
to productive programming. I'll leave this point as one of violent
disagreement...
+ Efficiency: As Paul Graham puts it, efficiency comes from profiling.
In order to achieve efficiency, you need to identify the bottle-necks of
your program. No amount of static checks can identify bottle-necks, you
have to actually run the program to determine them.

I don't think you understand much about language implementation.
A strong, expressive, static type system provides for optimisations
that cannot be done any other way. These optimizations alone can be
expected to make a program several times faster. For example:
- no run-time type checks need be performed;
- data representation is automatically optimised by the compiler
(e.g. by pointer tagging);
- polymorphic code can be inlined and/or specialised according to each
application;
- if the language does not support dynamic typing then values need not
carry their own type identifiers around with them, thereby saving
space;
- if the language does support explicit dynamic typing, then only
those places using that facility need plumb in the type identifiers
(something done automatically by the compiler.)

On top of all that, you can still run your code through the profiler,
although the need for hand-tuned optimization (and consequent code
obfuscation) may be completely obviated by the speed advantage
conferred by the compiler exploiting a statically checked type system.
I wouldn't count the use of java.lang.Object as a case of dynamic
typing. You need to explicitly cast objects of this type to some class
in order to make useful method calls. You only do this to satisfy the
static type system. (BTW, this is one of the sources for potential bugs
that you don't have in a decent dynamically typed language.)

No! A thousand times, no!

Let me put it like this. Say I have a statically, expressively, strongly
typed language L. And I have another language L' that is identical to
L except it lacks the type system. Now, any program in L that has the
type declarations removed is also a program in L'. The difference is
that a program P rejected by the compiler for L can be converted to a
program P' in L' which *may even appear to run fine for most cases*.
However, and this is the really important point, P' is *still* a
*broken* program. Simply ignoring the type problems does not make
them go away: P' still contains all the bugs that program P did.
I don't think so.

Yes, but your arguments are unconvincing. I should point out that
most of the people on comp.lang.functional (a) probably used weakly/
dynamically typed languages for many years, and at an expert level,
before discovering statically typed (declarative) programming and
(b) probably still do use such languages on a regular basis.
Expressive, static typing is not a message shouted from ivory towers
by people lacking real-world experience.

Why not make the argument more concrete? Present a problem
specification for an every-day programming task that you think
seriously benefits from dynamic typing. Then we can discuss the
pros and cons of different approaches.

-- Ralph
 
P

Pascal Costanza

Matthias said:
By whose definition? What *is* your definition of "useful"? It is
clear to me that static typing improves maintainability, scalability,
and helps with the overall design of software. (At least that's my
personal experience, and as others can attest, I do have reasonably
extensive experience either way.)

A 100,000 line program in an untyped language is useless to me if I am
trying to make modifications -- unless it is written in a highly
stylized way which is extensively documented (and which usually means
that you could have captured this style in static types). So under
this definition of "useful" it may very well be that there are fewer
programs which are useful under dynamic typing than there are under
(modern) static typing.

A statically typed program is useless if one tries to make modifications
_at runtime_. There are software systems out there that make use of
dynamic modifications, and they have a strong advantage in specific
areas because of this.

If you can come up with a static type system for an unrestricted runtime
metaobject protocol, then I am fine with static typing.
There are also programs which I cannot express at all in a purely
dynamically typed language. (By "program" I mean not only the executable
code itself but also the things that I know about this code.)
Those are the programs which are protected against certain bad things
from happening without having to do dynamic tests to that effect
themselves.

This is a circular argument. You are already suggesting the solution in
your problem description.
(Some of these "bad things" are, in fact, not dynamically
testable at all.)

For example?
Don't fear. I will.

....and BTW, please let me keep up using dynamically typed languages,
because this works well for me!

(That's the whole of my answer to the original question, why one would
want to give up static typing.)
This assumes that there is a monotone function which maps token count
to error-proneness and that the latter depends on nothing else. This
is a highly dubious assumption. In many cases the few extra tokens
you write are exactly the ones that let the compiler verify that your
thinking process was accurate (to the degree that this fact is
captured by types). If you get them wrong *or* if you got the
original code wrong, then the compiler can tell you. Without the
extra tokens, the compiler is helpless in this regard.

See the example of downcasts in Java.
To make a (not so far-fetched, btw :) analogy: Consider logical
statements and formal proofs. Making a logical statement is easy and
can be very short. It is also easy to make mistakes without noticing;
after all saying something that is false while still believing it to
be true is extremely easy. Just by looking at the statement it is
also often hard to tell whether the statement is right. In fact,
computers have a hard time with this task, too. Theorem-proving is
hard.
On the other hand, writing down the statement with a formal proof is
impossible to get wrong without anyone noticing because checking the
proof for validity is trivial compared to coming up with it in the
first place. So even though writing the statement with a proof seems
harder, once you have done it and it passes the proof checker you can
rest assured that you got it right. The longer "program" will have fewer
"bugs" on average.

Yes, but then you have a proof that is tailored to the statement you
have made. The claim of people who favor static type systems is that
static type systems are _generally_ helpful.


Pascal
 
P

Pascal Costanza

Andrew said:
Pascal Costanza:



Ummm, both are infinite and both are countably infinite, so those sets
are the same size. You're falling for Hilbert's Paradox.

Also, while I don't know a proof, I'm pretty sure that type inferencing
can do addition (and theorem proving) so is equal in power to
programming.

Just give me a static type system CLOS + MOP.
The size comparisons I've seen (like the great programming language
shootout) suggest that Ocaml and Scheme require about the same amount
of code to solve small problems. Yet last I saw, Ocaml is strongly typed
at compile time. How do you assume then that strongly&statically typed
languages require "considerable more code"?

_small_ problems?


Pascal
 
F

Fergus Henderson

Lulu of the Lotus-Eaters said:
I also read c.l.functional (albeit only lightly). In the last 12
months, I have encountered dozens of complaints about over-restrictive
type sytems in Haskell, OCaml, SML, etc.

The trick is that these complaints are not phrased in precisely that
way. Rather, someone is trying to do some specific task, and has
difficulty arriving at a usable type needed in the task. Often posters
provide good answers--Durchholz included. But the underlying complaint
-really was- about the restrictiveness of the type system.

Could you provide a link to an example of such a post?

In my experience, people who have difficulties in getting their programs
to typecheck usually have an inconsistent design, not a design which is
consistent but which the type checker is too restrictive to support.
 
P

Pascal Costanza

Marshall said:
Huh? The explicit-downcast construct present in Java is the
programmer saying to the compiler: "trust me; you can accept
this type of parameter." In a dynamically-typed language, *every*
call is like this! So if this is a source of errors (which I believe it
is) then dynamically-typed languages have this potential source
of errors with every function call, vs. statically-typed languages
which have them only in those few cases where the programmer
explicitly puts them in.

What can happen in Java is the following:

- You might accidentally use the wrong class in a class cast.
- For the method you try to call, there happens to be a method with the
same name and signature in that class.

In this situation, the static type system would be happy, but the code
is buggy.

In a decent dynamically typed language, you have proper name space
management, so that a method cannot ever be defined for a class only by
accident.

(Indeed, Java uses types for many different unrelated things - in this
case as a very weak name space mechanism.)


Pascal
 
P

Pascal Costanza

Ralph said:
This is utterly bogus. If you write unit tests beforehand, you are
already pre-specifying the interface that the code to be tested will
present.

I fail to see how dynamic typing can confer any kind of advantage here.

Read the literature on XP.
Are you seriously claiming that concise, *automatically checked*
documentation (which is one function served by explicit type
declarations) is inferior to unchecked, ad hoc commenting?

I am sorry, but in my book, assertions are automatically checked.
For one thing, type declarations *cannot* become out-of-date (as
comments can and often do) because a discrepancy between type
declaration and definition will be immidiately flagged by the compiler.

They same holds for assertions as soon as they are run by the test suite.
I think Fergus was referring to static error checking, but (and forgive
me if I'm wrong here) that's a feature you seem to insist has little or
no practical value - indeed, you seem to claim it is even an impediment
to productive programming. I'll leave this point as one of violent
disagreement...

It has value for certain cases, but not in general.
I don't think you understand much about language implementation.

....and I don't think you understand much about dynamic compilation. Have
you ever checked some not-so-recent-anymore work about, say, the HotSpot
virtual machine?
A strong, expressive, static type system provides for optimisations
that cannot be done any other way. These optimizations alone can be
expected to make a program several times faster. For example:
- no run-time type checks need be performed;
- data representation is automatically optimised by the compiler
(e.g. by pointer tagging);
- polymorphic code can be inlined and/or specialised according to each
application;
- if the language does not support dynamic typing then values need not
carry their own type identifiers around with them, thereby saving
space;
- if the language does support explicit dynamic typing, then only
those places using that facility need plumb in the type identifiers
(something done automatically by the compiler.)

You are only talking about micro-efficiency here. I don't care about
that, my machine is fast enough for a decent dynamically typed language.
On top of all that, you can still run your code through the profiler,
although the need for hand-tuned optimization (and consequent code
obfuscation) may be completely obviated by the speed advantage
conferred by the compiler exploiting a statically checked type system.

Have you checked this?
No! A thousand times, no!

Let me put it like this. Say I have a statically, expressively, strongly
typed language L. And I have another language L' that is identical to
L except it lacks the type system. Now, any program in L that has the
type declarations removed is also a program in L'. The difference is
that a program P rejected by the compiler for L can be converted to a
program P' in L' which *may even appear to run fine for most cases*.
However, and this is the really important point, P' is *still* a
*broken* program. Simply ignoring the type problems does not make
them go away: P' still contains all the bugs that program P did.

You are making several mistakes here. I don't argue for languages that
don't have a type system, I argue for languages that are dynamically
typed. We are not debating strong typing.

Furthermore, a program P that is rejected by L is not necessarily broken.
Yes, but your arguments are unconvincing. I should point out that
most of the people on comp.lang.functional (a) probably used weakly/
dynamically typed languages for many years, and at an expert level,
before discovering statically typed (declarative) programming and

Weak and dynamic typing is not the same thing.
(b) probably still do use such languages on a regular basis.
Expressive, static typing is not a message shouted from ivory towers
by people lacking real-world experience.
Why not make the argument more concrete? Present a problem
specification for an every-day programming task that you think
seriously benefits from dynamic typing. Then we can discuss the
pros and cons of different approaches.

No. The original question asked in this thread was along the lines of
why abandon static type systems and why not use them always. I don't
need to convince you that a proposed general solution doesn't always
work, you have to convince me that it always works.

Otherwise I could come up with some other arbitrary restriction and
claim that it is a general solution for writing better programs, and ask
you to give counter-examples as well. This is not a reasonable approach
IMHO.

There are excellent programs out there that have been written with
static type systems, and there are also excellent programs out there
that have been written without static type systems. This is a clear
indication that static type systems are not a necessary condition for
writing excellent programs.

Furthermore, there are crap programs out there that have been written
with static type systems, so a static type system is also not a
sufficient condition for writing good software.

The burden of proof is on the one who proposes a solution.


Pascal
 
P

Pascal Costanza

Fergus said:
In my experience, people who have difficulties in getting their programs
to typecheck usually have an inconsistent design, not a design which is
consistent but which the type checker is too restrictive to support.

Have you made sure that this is not a circular argument?

Does "consistent design" mean "acceptable by a type checker" in your book?


Pascal
 
J

Joachim Durchholz

Andrew said:
If a few rockets blow up for testing then it's still cheaper than
quintupling the development costs.

Not quite - that was a loss of 500 million dollars. I don't know what
the software development costs were, so I'm just guessing here, but I
think it's relatively safe to assume a doubly redundant system would
already have paid off if it had caught the problem.

The point is that no amount of software technology would have caught the
problem if the specifications are wrong. I think it would have been more
successful if there had been some automated specification checking,
which is safely in the area of theorem proving - which has interesting
connections to static type checking but is otherwise unrelated to
programming.

Regards,
Jo
 
J

Joachim Durchholz

Pascal said:
For example, static type systems are incompatible with dynamic
metaprogramming. This is objectively a reduction of expressive power,
because programs that don't allow for dynamic metaprogramming can't be
extended in certain ways at runtime, by definition.

What is dynamic metaprogramming?

Regards,
Jo
 
H

Holger Krekel

Dirk said:
IMHO it helps to think about static typing as a special kind of unit
tests. Like unit tests, they verify that for some input values, the
function in question will produce the correct output values. Unlike
unit tests, they do this for a class of values, instead of testing
statistically by example. And unlike unit tests, they are pervasive:
Every execution path will be automatically tested; you don't have
to invest brain power to make sure you don't forget one.

IMHO typical unit-tests in python go a lot further than just testing
types. They test *behaviour* rather than just types. Thus I tend to
think that for languages like python unittests are a *perfect match*
because there is hardly any redundancy and they are very short to write
down usually. Writing unittests in a statically typed language is more
redundant because - like you say - type declarations already are a kind
of (IMO very limited) tests.

cheers,

holger
 
P

Pascal Costanza

Dirk said:
IMHO it helps to think about static typing as a special kind of unit
tests. Like unit tests, they verify that for some input values, the
function in question will produce the correct output values. Unlike
unit tests, they do this for a class of values, instead of testing
statistically by example. And unlike unit tests, they are pervasive:
Every execution path will be automatically tested; you don't have
to invest brain power to make sure you don't forget one.

This is clear.
Type inference will automatically write unit tests for you (besides
other uses like hinting that a routine may be more general than you
thought). But since the computer is not very smart, they will test
only more or less trivial things. But that's still good, because then
you don't have to write the trivial unit tests, and only have to care
about the non-trivial ones.

Unless the static type system takes away the expressive power that I need.
Type annotations are an assertion language that you use to write down
that kind of unit tests.
Yep.

Of course you can replace the benefits of static typing by enough unit
tests. But they are different verification tools: For some kind of
problems, one is better, for other kinds, the other. There's no reason
not to use both.

I have given reasons when not to use a static type system in this
thread. Please take a look at the Smalltalk MOP or the CLOS MOP and tell
me what a static type system should look like for these languages!


Pascal
 
J

Joachim Durchholz

Andrew said:
Pascal Costanza:


Ummm, both are infinite and both are countably infinite, so those sets
are the same size. You're falling for Hilbert's Paradox.

The sets in question are not /all/ dynamically/statically typed
programs, they are all dynamically/statically typed programs that fit
any item in the set of specifications in existence. Which is a very
finite set.
Also, while I don't know a proof, I'm pretty sure that type inferencing
can do addition (and theorem proving) so is equal in power to
programming.

Nope. It depends on the type system used: some are decidable, some are
undecidable, and for some, decidability is unknown.

Actually, for decidable type inference systems, there's also the
distinction between exponential, polynomial, O (N log N), and linear
behaviour; for some systems, the worst-case behaviour is unknown but
benevolent in practice.

The vast majority of practical programming languages use a type
inference system where the behavior is known to be O (N log N) or better :)
(meaning that the other type systems and associated inference algorithms
are research subjects and/or research tools)

Regards,
Jo
 
J

Joachim Durchholz

Pascal said:
Have you made sure that this is not a circular argument?

Does "consistent design" mean "acceptable by a type checker" in your book?

Matthias Blume already posted that the type checker caught several
design errors. He's not the only to post such reports, I regularly
statements like that on comp.lang.functional.

Regards,
Jo
 
J

Joachim Durchholz

Pascal said:
Read the literature on XP.

Note that most literature contrasts dynamic typing with the static type
systems of C++ and/or Java. Good type systems are /far/ better.
I am sorry, but in my book, assertions are automatically checked.

But only at runtime, where a logic flaw may or may not trigger the
assertion.
(Assertions are still useful: if they are active, they prove that the
errors checked by them didn't occur in a given program run. This can
still be useful. But then, production code usually runs with assertion
checking off - which is exactly the point where knowing that some bug
occurred would be more important...)
They same holds for assertions as soon as they are run by the test suite.

A test suite can never catch all permutations of data that may occur (on
a modern processor, you can't even check the increment-by-one operation
with that, the universe will end before the CPU has counted even half of
the full range).
....and I don't think you understand much about dynamic compilation.
Have you ever checked some not-so-recent-anymore work about, say, the
HotSpot virtual machine?

Well, I did - and the results were, ahem, unimpressive.
Besides, HotSpot is for Java, which is statically typed, so I don't
really see your point here... unless we're talking about different VMs.

And, yes, VMs got pretty fast these days (and that actually happened
several years ago).
It's only that compiled languages still have a good speed advantage -
making a VM fast requires just that extra amount of effort which, when
invested into a compiler, will make the compiled code still run faster
than the VM code.
Also, I have seen several cases where VM code just plain sucked
performance-wise until it was carefully hand-optimized. (A concrete
example: the all-new, great graphics subsystem for Squeak that could do
wonders like rendering fonts with all sorts of funky effects, do 3D
transformations on the fly, and whatnot... I left Squeak before those
optimizations became mainstream, but I'm pretty sure that Squeak got
even faster. Yet Squeak is still a bit sluggish... only marginally so,
and certainly no more sluggish than the bloatware that's around and that
commercial programmers are forced to write, but efficiency is simply
more of a concern and a manpower hog than with a compiled language.)
There are excellent programs out there that have been written with
static type systems, and there are also excellent programs out there
that have been written without static type systems. This is a clear
indication that static type systems are not a necessary condition for
writing excellent programs.

Hey, there are also excellent programs written in assembly. By your
argument, using a higher language is not a necessary condition for
writing excellent languages.

The question is: what effort goes into an excellent program? Is static
typing a help or a hindrance?

One thing I do accept: that non-inferring static type systems like those
of C++ and Java are a PITA. Changing a type in some interface tends to
cost a day or more, chasing all the consequences in callers, subclasses,
and whatnot, and I don't need that (though it does tell me all the
places where I should take a look to check if the change didn't break
anything, so this isn't entirely wasted time).
I'm still unconvinced that an inferring type system is worse than
run-time type checking. (Except for that "dynamic metaprogramming" thing
I'd like to know more about. In my book, things that are overly powerful
are also overly uncontrollable, but that may be an exception.)

Regards,
Jo
 
J

Joachim Durchholz

Pascal said:
See the example of downcasts in Java.

Please do /not/ draw your examples from Java, C++, or Eiffel. Modern
static type systems are far more flexible and powerful, and far less
obtrusive than the type systems used in these languages.

A modern type system has the following characteristics:

1. It's safe: Code that type checks cannot assign type-incorrect values
(as opposed to Eiffel).

2. It is expressive: There's no need to write type casts (as opposed to
C++ and Java). (The only exceptions where type casts are necessary are
those where it is logically unavoidable: e.g. when importing binary data
from an untyped source.)

3. It is unobtrusive: The compiler can infer most if not all types by
itself. Modifying some code so that it is slightly more general will
thus automatically acquire the appropriate slightly more general type.

4. It is powerful: any type may have other types as parameters. Not only
for container types such as Array <Integer>, but also for other
purposes. Advanced type systems can even express mutually recursive
types - an (admittedly silly) example: trees that have alternating node
types on paths from root to leaves. (And all that without type casts,
Mum! *g*)

Regards,
Jo
 
A

Andreas Rossberg

Joachim said:
The vast majority of practical programming languages use a type
inference system where the behavior is known to be O (N log N) or better

Not true, unfortunately. Type inference for almost all FP languages is a
derivative from the original Hindley/Milner algorithm for ML, which is
known to have exponential worst-case behaviour. Interestingly, such
cases never show up in practice, most realistic programs can be checked
in subquadratic time and space. For that reason even the inventors of
the algorithm originally believed it was polynomial, until somebody
found a counterexample.

The good news is that, for similar reasons, undecidable type checking
need not be a hindrance in practice.

- Andreas

--
Andreas Rossberg, (e-mail address removed)-sb.de

"Computer games don't affect kids; I mean if Pac Man affected us
as kids, we would all be running around in darkened rooms, munching
magic pills, and listening to repetitive electronic music."
- Kristian Wilson, Nintendo Inc.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,174
Messages
2,570,940
Members
47,484
Latest member
JackRichard

Latest Threads

Top