Python from Wise Guy's Viewpoint

R

Raffael Cavallaro

Matthias Blume said:
[This whole discussion is entirely due to a mismatch of our notions of
what constitutes expressive power.]

No, it is due to your desire to be type constrained inappropriately
early in the development process. Lispers know that early on, you
don't care about type constraints because you haven't settled on your
final data representations yet. So why waste time placating a
type-checking compiler before you have to?

With lisp, you only add as much type checking as you need, *when* you
need it.

With a statically typed language, you have to do the compiler type
check dance from the very first minute, even though you don't need to
solve the type constraint problems yet.
 
M

Matthias Blume

[ on my claim that every dynamically typed program has a statically typed
translation: ]
No, that's not all you need to do. Essentially you would need to write
a complete interpreter/compiler for the dynamically typed language on
top of the statically typed one, _if_ you want runtime
metaprogramming. That's not what I would call a straightforward
translation.

Actually, I didn't say what I need, so how can you contradict me on
this point? Anyway, having to have a compiler/interpreter around is
only true if the original program contained a complete
compiler/interpreter, in which case I'd say this is just par for the
course.
[This whole discussion is entirely due to a mismatch of our notions of
what constitutes expressive power.]

No, it's not. There's a class of programs that exhibit a certain
behavior at runtime that you cannot write in a statically typed
language _directly in the language itself_.

This is simply not true. See above.
There's no program that
exhibits a certain behavior at runtime that you can only write in a
statically typed language. [1]

I do not dispute this fact.
[11 Except perhaps for a class of programs that would change their
runtime and/or space complexity, provided they would need lots of
dynamic type checks.

This comment makes me /very/ uneasy. Namely, the funny thing here
is that you seem to question a statement that you make /yourself/ even
though it is undoubtedly true. *Of course* you can write everything
that can be written in a statically typed language in a dynamically
typed language in such a way that runtime behavior is the same
(including complexity). Translating a well-typed ML program into,
say, Lisp is completely straightforward. (Early ML implementations
were done that way.)

The proof of type-safety for the original ML program also shows that
no additional runtime checks are needed for the result of the
translation. You don't even need the runtime checks that Lisp does on
its own because it doesn't know any better. (This is unless you turn
these checks off explicitly -- which in general would make the code
unsafe but in this specific case does not.)

So rest assured that the answer to your question:
But I haven't sorted out yet whether this class
really exists.

is "no". But that was not my point anyway.

Matthias

PS: You don't need to try and convince me of the virtues of dynamic
typing. I *come* from this world -- having implemented at least three
(if not four -- depending on how you count) interpreters/compilers for
dynamically typed languages. Because of this I also already know all
the arguments that you are undoubtedly thinking of bringing forward,
having either used them myself or once marveled at them when I heard
them from other, then like-minded folks. But the long-term effect of
working with and on such systems was that I now prefer to avoid them.
"Run-time metaprogrammming" you say? The emperor has no clothes.
 
A

Adrian Hey

Raffael said:
This is sophistry at its worst. If you "translate" a dynamically typed
program into a statically typed language by eliminating all the static
type checking, then WTF is the point of the static type checking?

Read what he wrote..in particular the words *WORST CASE SCENARIO*

Regards
 
F

Fergus Henderson

Pascal Costanza said:
This proves that a static type system requires you to write more code
than strictly necessary.

True in the sense that "there exists a static type system that requires ...".
That is true for several static type systems that I know --
but the amount of extra code required is very very small.
E.g. in Haskell it is usually sufficient to just write

concretemethod = error "stub"

However, your statement would be false if you tried to generalize it to
all languages and language implementations that have static type systems.
As I said, it would be easy to modify a statically typed system to
optionally allow references to undefined functions, and indeed there
are some systems which do that. (For example I think ObjectCenter,
and interpretive environment for C/C++ programs, did that.)

If a couple of potential users of Mercury were to ask for it, I would
go ahead and add support for this to the Mercury implementation. But
so far, to the best of my knowledge, no Mercury user has ever asked for it.
A good development environment gives you immediate feedback on such
kinds of typos. A good compiler for a dynamically type language issues a
warning. So these typos don't go unnoticed.

My experience is that compiler warnings are too easily ignored.

As for error highlighting in IDEs, well... Firstly, as you yourself
mentioned, not everyone wants to use a complicated IDE.

Secondly, even in such an IDE, I think errors could still slip through
unnoticed. For example, consider the following scenario. You might
write a call to a new function, which will get highlighted. But you
expect that, since the function is not yet defined so you ignore the
highlighting. Then you write another call to the function, which also
gets highlighted, and again you ignore it, since you expected that.
Finally you write the definition of the function, and run the program.
The compiler reports a few dozen warnings, which you ignore, since they
all seem to be referring to some undefined stubs in part of the program
that one of your colleagues is responsible for. Then you run a few tests,
which all work, so you check your change in to the shared repository and
go home for the weekend.

Your colleague, who is working all weekend to get things ready for an
important demo, does a cvs update and incorporates your change. But when
he runs _his_ test case, it now crashes with an error about
"undefined function 'tingamajig_handler'"! He greps the source
for "tingamajig", but the only occurrence is the one single place
where it is called, which happens to have absolutely no comments about
what it is supposed to do. In desparation, he tries calling you,
but your mobile phone's battery has run flat. He tries to implement
"tingamajig_handler" himself, but has no idea of what invariants it
is supposed to enforce, and eventually gives up. The demo on Monday
morning is a complete failure.

On Monday afternoon, he finally catches up with you, and tells you of his
woes. You see immediately that the problem was just a simple typo --
the function was named "thingamajig_handler", not "tingamajig_handler".


A far-fetched hypothetical? Possibly. But if you tell us that
"typos don't go unnoticed", please forgive me if I am a little skeptical ;-)
 
K

ketil+news

That's not "ideal" at all, to me. I find the automatic conversions in
C a paint in the b*tt

You're making the mistake of comparing to C :) And it's much worse
in C++, when conversions in conjunction with overloading can impact
which function gets called in non-obvious ways.

Anyway, I think Haskell makes this work better. Still no automatic
conversions, but more flexibility in the use of numeric literals.
Occasionally, you need to sprinkle "fromIntegral"s around in
expressions, which I agree aren't terribly pretty.

-kzm
 
M

Matthew Danish

It is more than enough evidence that there are programs which require
cycle-level efficiency and therefore cannot afford to use infinite
precision arithmetic.

Said programs will then use specialized data types.
It is, in my opinion, ridiculous to claim that most programs should
use infinite precision integers in almost all places (i.e., by
default). Infinite precision is rarely necessary and almost always
wasteful overkill.

How is it wasteful? Numbers can still be represented by the most
efficient representation available, while retaining infinite precision
when operated upon. Do you realize that Common Lisp implementations
represent integers in the range of a fixnum as machine words? And
automatically convert them to bignum objects when they overflow?
Declarations can take this further, such that a compiler as smart as
CMUCL can manipulate raw (unsigned-byte 32) values, for example.

Are the vast majority of your programs the type which behave properly
within machine-word integers? Do they take into account the possibility
of overflow? Should someone have to worry about issues like overflow
when they just want to write a simple arithmetic program, in a high
level language?
idea that the only correct result of 20 * 30 has to be 600.)

(20 * 30) mod 256 is, of course, a completely different expression.
 
P

Pascal Costanza

Stephen said:
As I wrote, I'm a low tech guy, I put all the tests for a particular
feature in the same file. If I only want to run some of the tests in
the file then I comment out those tests. If I only want to run the
tests in some file rather than others then I comment out the names of
the files containing the tests I don't want to run. I can see how
things can get more complicated if you use other approaches, which is
one of the reasons I don't use those approaches. YMMV.





Er, comment out either definition of the macro and the calls to it or
the code generation facility.

These are both all or nothing solutions.

+ "all the tests for a particular feature in one place" - maybe that's
not what I want (and you have ignored my arguments in this regard)

and:
+ what if I want to run _some_ of the tests that my macro produces but
not _all_ of them?


Actually, that seems to be the typical reaction of static typing fans.
This reminds me of a joke.

Imagine yourself back in the 1980's. A general of the former Soviet
Union says: "We can travel anywhere we want." Question: "What about
Great Britain, Italy, France, the US?" "We don't want to travel there."


Pascal
 
F

Fergus Henderson

Pascal Costanza said:
You can't have metacircularity in a statically type language.

Could you explain exactly what you mean by "metacircularity"?

Anyway, I'm skeptical of this claim. At very least, it should be possible
to have a language which is mostly statically typed (i.e. statically
typed by default), even if on some occaisions you have to fall back to
dynamic typing.

Whether or not any existing statically typed language implementations
support this sort of thing is another question...
 
P

Pascal Costanza

Stephen said:
Pascal Costanza said:
is that for many algorithms people want to be sure that the compiler
represents their values in machine words. Infinite precision is
needed sometimes, but in the majority of cases it is overkill. If you
need infinite precision, specify the type (IntInf.int in SML's case).
A clever compiler might optimize that like a Lisp compiler does. In
most other cases, why take any chances?

I disagree strongly here. I am convinced that in most algorithms,
machine words don't matter at all. Have you ever seen in books on
algorithms that they actually _need_ to restrict them to values that
are representable in machine word sizes?
[snip]
Computers are fast enough and have enough memory nowadays. You are
talking about micro efficiency. That's not interesting anymore.


Implement something like MD5, SHA-1, AES, ... etc. in your favourite
language and use the fastest compiler available to you to calculate
how many MB/s it can process. If it can get say with a factor of 2 of
C code then IMHO you'll have proved your point. If not, then either
your point stands but your favourite language doesn't have
sufficiently good compilers available yet, or exact sizes are required
in order to get good performance in this case.

Are these algorithms reason enough to have machine word sized numerical
data types as the default for a _general purpose_ language?


Pascal
 
F

Fergus Henderson

Pascal Costanza said:
Can you show me an example of a program that does't make sense anymore
when you strip off the static type information?

Here's a C++ example:

x << y

Depending on the types of x and y, this might mean left shift, I/O,
or something entirely different.

Here's another C++ example:

#include "foo.h"
main() {
auto Foo x; // constructor has side-effects
}

If you strip away the static type information here, i.e. change "auto Foo x;"
to just "auto x;", then there's no way to know which constructor to call!
 
F

Fergus Henderson

Pascal Costanza said:
You don't need to check the type on each access. If you only copy a
value from one place to other, and both places are untyped, you don't
need any check at all.

Great. I feel so much better now. Now my type errors are free to
propagate throughout my program's data structures, so that when they
are finally detected, it may be far from the true source of the problem.

But the example that I was thinking of did actually want to access the
value, not just copy it.
Furthermore, if I remember correctly, dynamically compiled systems use
type inferencing at runtime to reduce the number of type checks.

In cases such as the one described above, they may reduce the number of
times that the type of the _collection_ is checked, but they won't be
able to avoid checking the element type at every element access.
 
F

Fergus Henderson

Pascal Costanza said:
Hmm, could a kind of "meta type system protocol" be feasible? I.e., a
language environment in which you could tweak the type system to your
concrete needs, without having to change the language completely?

Yes. But that is still a research issue at this stage.
For some work in this area, see the following references:

[1] Martin Sulzmann, "A General Type Inference Framework for
Hindley/Milner Style Systems." In 5th International Symposium
on Functional and Logic Programming (FLOPS), Tokyo, Japan,
March 2001.

[2] Sandra Alves and Mario Florido, "Type Inference using
Constraint Handling Rules", Electronic Notes in Theoretical
Computer Science volume 64, 2002.

[3] Kevin Glynn, Martin Sulzmann, Peter J. Stuckey,
"Type Classes and Constraint Handling Rules",
University of Melbourne Department of Computer Science
and Software Engineering Technical Report 2000/7, June 2000.

Also the work on dependent type systems, e.g. the language Cayenne,
could be considered to fit in this category.
 
A

Andreas Rossberg

Pascal said:
Hmm, could a kind of "meta type system protocol" be feasible? I.e., a
language environment in which you could tweak the type system to your
concrete needs, without having to change the language completely?

Well, such "protocols" usually come in the form of compiler switches or
pragmas. ;-)

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

Andreas Rossberg

Raffael said:
Matthias Blume said:
[This whole discussion is entirely due to a mismatch of our notions of
what constitutes expressive power.]

No, it is due to your desire to be type constrained inappropriately
early in the development process.

Oh my. How is this related?

I think Matthias' is absolutely right. The mismatch here is that some
fail to understand that - obviously, one should hope - the ability to
express restrictions is an ability to express something, i.e. expressive
power. Otherwise assertions, pre/post conditions, probably exceptions
and similar stuff wouldn't carry any expressive power either. Which of
course is nonsense.
Lispers know that early on, you
don't care about type constraints because you haven't settled on your
final data representations yet.

Another repeated misunderstanding. When I use types in early coding
phases in ML for example, these types are mostly abstract. They don't
say anything about representations. All checking takes place against
abstract types, whose representation is fully exchangable.
With lisp, you only add as much type checking as you need, *when* you
need it.

Yes, and you also loose most of the benefits of typing...

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

Matthias Blume

Matthew Danish said:
Declarations can take this further, such that a compiler as smart as
CMUCL can manipulate raw (unsigned-byte 32) values, for example.

Oh, so you're saying you want static declarations, checked and
enforced by the compiler? Hmm, I've read this point of view in this
thread somewhere.
Are the vast majority of your programs the type which behave properly
within machine-word integers?

(20 * 30) mod 256 is, of course, a completely different expression.

Undoubtedly, it is a different expression. But it might mean the
same, given a correspondingly chosen domain for 20 and 30, together
with an certain * operation.

Matthias
 
P

Pascal Costanza

Matthias said:
This is simply not true. See above.

OK, let's try to distill this to some simple questions.

Assume you have a compiler ML->CL that translates an arbitrary ML
program with a main function into Common Lisp. The main function is a
distinguished function that starts the program (similar to main in C).
The result is a Common Lisp program that behaves exactly like its ML
counterpart, including the fact that it doesn't throw any type errors at
runtime.

Assume furthermore that ML->CL retains the explicit type annotations in
the result of the translation in the form of comments, so that another
compiler CL->ML can fully reconstruct the original ML program without
manual help.

Now we can modify the result of ML->CL for any ML program as follows. We
add a new function that is defined as follows:

(defun new-main ()
(loop (print (eval (read)))))

(We assume that NEW-MAIN is a name that isn't defined in the rest of the
original program. Otherwise, it's easy to automatically generate a
different unique name.)

Note that we haven't written an interpreter/compiler by ourselves here,
we just use what the language offers by default.

Furthermore, we add the following to the program: We write a function
RUN (again a unique name) that spawns two threads. The first thread
starts the original main function, the second thread opens a console
window and starts NEW-MAIN.

Now, RUN is a function that executes the original ML program (as
translated by ML->CL, with the same semantics, including the fact that
it doesn't throw any runtime type errors in its form as generated by
ML->CL), but furthermore executes a read-eval-print-loop that allows
modification of the internals of that original program in arbitrary
ways. For example, the console allows you to use DEFUN to redefine an
arbitrary function of the original program that runs in the first
thread, so that the original definition is not visible anymore and all
calls to the original definiton within the first thread use the new
definition after the redefinition is completed. [1]

Now here come the questions.

Is it possible to modify CL->ML in a way that any program originally
written in ML, translated with ML->CL, and then modified as sketched
above (including NEW-MAIN and RUN) can be translated back to ML? For the
sake of simplicity we can assume an implementation of ML that already
offers multithreading. Again, for the sake of simplicity, it's
acceptable that the result of CL->ML accepts ML as an input language for
the read-eval-print-loop in RUN instead of Common Lisp. The important
thing here is that redefinitions issued in the second thread should
affect the internals of the program running in the first thread, as
described above.

To ask the question in more detail:

a) Is it possible to write CL->ML in a way that the result is both still
statically type checkable and not considerably larger than the original
program that was given to ML->CL. Especially, is it possible to do this
without implementing a new interpreter/compiler on top of ML and then
letting the program run in that language, but keep the program
executable in ML itself?

(So, ideally, it should be roughly only as many lines longer as the
modified CL version is compared to the unmodified CL version.)

b) Otherwise, is there a way to extend ML's type system in a way that it
is still statically checkable and can still handle such programs?

c) If you respond with yes to either a or b, what does your sketch of an
informal proof in your head look like that convincingly shows that this
can actually work?

d) If you respond with no to both a or b, would you still disagree with
the assessment there is a class of programs that can be implemented with
dynamically typed languages but without statically typed ones? If so, why?
[11 Except perhaps for a class of programs that would change their
runtime and/or space complexity, provided they would need lots of
dynamic type checks.


This comment makes me /very/ uneasy. Namely, the funny thing here
is that you seem to question a statement that you make /yourself/ even
though it is undoubtedly true. *Of course* you can write everything
that can be written in a statically typed language in a dynamically
typed language in such a way that runtime behavior is the same
(including complexity). Translating a well-typed ML program into,
say, Lisp is completely straightforward. (Early ML implementations
were done that way.)

Well, nothing in this thread makes me uneasy at all. I am not trying to
defend dynamic typing out of pure personal preference. I want to find
out if we can objectively classify the programs that are expressible in
either statically typed languages or dynamically typed languages. Until
recently, I have thought that the class of programs you can write with a
dynamically typed language is a strict superset of the programs you can
write with a statically typed language. It is especially the class of
programs that allows full-fledged runtime metaprogramming, as sketched
above, that statically typed languages cannot implement by definition
without resorting to implement a full interpreter/compiler for a
dynamically typed language.

If it turns out that statically typed languages can indeed express a
class of programs that exhibit a certain behavior that you cannot write
in a dynamically typed language without implementing a full
interpreter/compiler for a statically typed language on top, this
wouldn't make me feel "uneasy". To the contrary, I would be happy
because I would finally understand what all the fuss about static typing
is about.

If it is your only concern that you defend your pet programming style,
well, that's not my problem. I am interested in insights.
PS: You don't need to try and convince me of the virtues of dynamic
typing. I *come* from this world -- having implemented at least three
(if not four -- depending on how you count) interpreters/compilers for
dynamically typed languages. Because of this I also already know all
the arguments that you are undoubtedly thinking of bringing forward,
having either used them myself or once marveled at them when I heard
them from other, then like-minded folks. But the long-term effect of
working with and on such systems was that I now prefer to avoid them.
"Run-time metaprogrammming" you say? The emperor has no clothes.

I don't care whether you have made bad experiences in this regard or
not, to more or less the same degree that you probably don't care
whether I have made bad experiences with static type systems. (And
that's fine.)

I am only asking questions that we can objectively answer. Can static
typing and runtime metaprogramming be reconciled, yes or no?


Pascal

[1] Yes, with all the dangers that this may incur! There are ways to
handle the potential dangers of such an approach. That's what dynamic
metaobject protocols are designed for. However, this doesn't matter for
the sake of this discussion.
 
E

Espen Vestre

Matthias Blume said:
Oh, so you're saying you want static declarations, checked and
enforced by the compiler?

CL declarations are nothing but hints to the compiler that it is
allowed to optimize. Sometimes this is useful.
Hmm, I've read this point of view in this thread somewhere.

Now you're conflating two readings of "want declarations" (i.e. "want
them whenever they're convenient for optimizing" vs. "want them
everywhere and always")
 
M

Mario S. Mommer

Matthias Blume said:
Oh, so you're saying you want static declarations, checked and
enforced by the compiler? Hmm, I've read this point of view in this
thread somewhere.

The point is that you can use static typing when you want. It doesn't
stand in the way when you don't need it, which is most of the time.
Undoubtedly, it is a different expression. But it might mean the
same, given a correspondingly chosen domain for 20 and 30, together
with an certain * operation.

Indeed. It could well be 42. Or 3.141592. Or maybe "hum". Who knows,
who knows.

Just change the type declarations and - violà! - popcorn.
 
P

Pascal Costanza

Fergus said:
Here's a C++ example:

x << y

Depending on the types of x and y, this might mean left shift, I/O,
or something entirely different.

And depending on the runtime types, this can be correctly dispatched at
runtime.
Here's another C++ example:

#include "foo.h"
main() {
auto Foo x; // constructor has side-effects
}

If you strip away the static type information here, i.e. change "auto Foo x;"
to just "auto x;", then there's no way to know which constructor to call!

But that's not type information, that's just a messy way to implicitly
call a function. (C++ confuses types and classes here.)


Pascal
 
P

Pascal Costanza

Fergus said:
Great. I feel so much better now. Now my type errors are free to
propagate throughout my program's data structures, so that when they
are finally detected, it may be far from the true source of the problem.

Now, you have changed the topic from optimization to catching errors
again. Could you please focus what you want to talk about?

And guess what, "in 99% of all cases, such type errors don't occur in
practice, at least not in my eperience". ;-P (Sorry, couldn't resist. I
sincerely hope you read this as a joke, and not as an attack. ;)
But the example that I was thinking of did actually want to access the
value, not just copy it.


In cases such as the one described above, they may reduce the number of
times that the type of the _collection_ is checked, but they won't be
able to avoid checking the element type at every element access.

Why? If the collection happens to contain only elements of a single type
(or this type at most), you only need to check write accesses if they
violate this condition. As long as they don't, you don't need to check
read accesses.

"In 99% of all cases, write accesses occur rarely, at least ..." - well,
you know the game. ;)


Pascal
 

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top