Steven D'Aprano a écrit :
On Tue, 03 Feb 2009 12:09:46 +0100, Bruno Desthuilliers wrote:
I love Python, and I'm greedy and want it all: I want a dynamic,
easy-to- use language *and* a compiler that can protect me from myself
That's not what compilers are for.
So you say.
While it's quite clear that a compiler for a statically typed language
can catch at least some type errors, this is still not what a compiler
is for. A compiler is here to compile you source code into a suitable
representation for the execution environment. And FWIW, some compilers
are rather weaker than Python when it comes to "protecting you from
yourself".
and bad data.
I definitly fail to see how a compiler could protect you from *runtime*
issues ???
I'm surprised you can't. Any time a compiler prevents an error by
detecting it at compile-time (say, attempting to store a 96-bit float
into the memory space allocated for a 32-bit int),
Oh, you meant "type error". But this is a programming error, not "bad
data". "bad data" is something you get (usually from the outside world)
at runtime only. Well, the way I understand it, at least.
Perhaps what you are thinking is that I meant compilers can protect you
from "all" runtime issues. That would be silly. Although, in principle,
we can get close, for some definition of "close".
Here's a toy function, and a specification:
def inverse(x):
return 1.0/x
Specification: input must be a float, output must be a float.
Is that function correct? No, because it will raise an exception if
x==0.0. Python's compiler is dumb and will allow you to write incorrect
functions, but a smart compiler could look at that function and see that
it was incorrect. The fix would then be to change the code, or the
specification, or both.
Ok, let's see. What happens if you pass 0.0 to the function as-is ? Yes,
indeed, you get a runtime error. Now what happens if you add error
handling to the function itself ? What will the error handler do ? Yes,
raise a runtime error. So far, I don't see any gain here. No, no, don't
answer yet. Sure, what you need here is a compiler that can inspect the
whole code source using this function to make sure it is always properly
invoked. Mmm, wait - will this work with shared libs ? I'm afraid not.
Ok, so you have to change the specification to : "input must be a
non-zero-float", and use the type system to define non-zero-float type.
Smart compilers turn some runtime errors into compile-time errors.
Indeed.
The
earlier you catch the error, the easier it is to fix it.
Indeed.
Now, that's a toy example. Languages like Ada make correctness proofs,
well, perhaps not easy, but merely difficult compared to impossible for
languages like Python.
Indeed. And that's fine : if we want maximum type safety and
as-possibly-proved code, we know which tool to use. Now good luck doing
web development, system admin, app scripting and quite a lot of other
things (well, perhaps more that 80% of ordinary application programming)
in Ada.
Can you understand that not everybody needs maximum type safety /
correctness proof etc for all his code ? That all this "security" stuff
comes with a price that may not be worth paying ?
To bring it back to private/public attributes, side-effects make
correctness proofs difficult. Modifications of attributes are side-
effects.
Then use a functional language !-)
When attributes are subject to being modified by arbitrary code,
it is harder to reason about the correctness of the code:
Indeed. Not the right tool, obviously. OTHO, it makes it way easier to
use as glue between different, sometimes not highly compatible components.
(snip)
There are two usual approaches to dealing with that problem which are
available to Python programmers:
(1) Paranoia. Make the developer responsible for checking everything all
the time:
Paranoiacs (also known as control-freaks) are not happy using Python.
They usually prefer Ada or, at least, Java.
def invert(self):
x = self.x
if isinstance(x, float) and x:
return 1.0/self.x
else:
raise MyError('post-condition x a non-zero float violated')
The correct solution is indeed to make x a property with a sanity check
in the setter - in which case you'll have a useful traceback - or as a
read-only property, or even better to just declare it as implementation
(naming it _x).
(2) Hope the error never occurs, and if it does, let the caller deal with
it.
If the attribute is either a read-only property or an implementation
attribute, then the guy that messed with it is responsible for the
possible consequences. That's the contract, yes.
Hopefully you aren't your own caller.
I'm usually the first user of my own libs, and my coworkers comes
immediatly after. IOW, I eat my own dogfood, thanks.
That's often the Python approach.
As I explained above, the "Python approach" is actually a bit different.
In your example code, you explicitely declared x as a *public*
attribute. Which is not wise - whatever the language - if you have to
maintain invariants on this attribute. Make it either an implementation
attribute or a read-only property.
(snip usual stuff about nuclear disasters, x-ray-overdosed patients etc)
There is a third strategy, sadly not available to Python programmers:
prevention by catching potential errors at compile-time. (Python does
catch some errors at compile-time, but only syntax errors.)
Yes. Because *everything* else happens at runtime. Remember, even "def"
and "class" are executable statements.
If x can only
be modified in a few places, (that is, it is effectively hidden or
private) then it is much easier to reason about program correctness,
avoid bugs, and debug those bugs which do occur.
Would you then say that Ruby has "real privates" attributes ?
No, this is not a panacea which will prevent every imaginable bug.
Indeed.
Nor is
it a replacement for unit-testing (which also does not prevent every
imaginable bug).
Neither.
--
http://mail.python.org/mailman/listinfo/python-list