Could Python supplant Java?

Paul Foley see at below.invalid
Sat Aug 24 05:35:52 CEST 2002


On Fri, 23 Aug 2002 10:14:08 -0700, James J Besemer wrote:

> Paul Foley wrote:

>> On Fri, 23 Aug 2002 04:12:48 -0700, James J Besemer wrote:
>> 
>> >      class A {
>> >          int x;
>> >          int bar(){...}
>> >      };
>> 
>> >      class B : A {
>> >          int y;
>> >          int bar(){...}
>> >      };
>> 
>> >      void foo( A a ){
>> >          a.x;
>> >          a.bar();    // calls A.bar()
>> >      }
>> 
>> Yes, I know.  But if you pass it a B instance, it ought to call
>> B.bar(), not A.bar().  What C++ does is Just Plain Wrong.

> Just Plain Wrong, certainly depends on what you're used to.

You mean if you're used to languages that do the wrong thing, you
probably don't notice that it's wrong?  Doesn't make it any less
wrong, though.

> C++ always tries to cast incoming arguments to the specified type if a cast is
> available.  This rule predates C++, having originated in C.

No, that's completely irrelevant.  Casting, automatic or otherwise,
really has nothing to do with it.

[Note: "cast" really has two completely different meanings in C and
C++.  When you "cast" a float to an int, what it does is really
coercion.  Casting pointers, or class instances, doesn't do the same
thing: it just tells the type system that you know better than it does
(which shouldn't be possible, if the type system wasn't broken to
begin with -- try ML for a non-broken static type system), and doesn't
actually change anything]

If you used "virtual" it would work, regardless of any casts.

> Casts are NOT automatically generated for pointers, so the following code WILL
> exhibit the expected Polymorphism without virtual functions (classes A and B as
> above):

>     void foo( A* arg ){
>         arg.x;
>         arg.bar();        // calls A or B depending on type of arg
>     }

>     A a; B b;

>     foo( &a );    // foo will call A.bar()
>     foo( &b );    // foo will call B.bar()

You obviously didn't try it.  It won't do what you say it will.

> I don't defend C++s semantics; I'm just trying to explain.  I think most people
> agree that casting args where possible overall is a Good Thing.  If you knew the
> language better you might not think it's so "Wrong."  But I won't argue that point.

I apparently know it better than you (you're undoubtedly more familiar
with the syntax and libraries and current idioms, but you clearly
don't understand how it works); consider the possibility that if you
knew it better, you might think it was more "Wrong".

> Anyway, C++ clearly does support Polymorphism without any "late binding".

Again, you've just put on a good demonstration that it doesn't.

>> > This is all completely static.
>> 
>> Yes.  Also completely lacking in polymorphism, so what was the point?

> Clearly we disagree on the definition of Polymorphism.  I think most authorities
> agree that C++ has Polymorphism as demonstrated above.

C++ does have polymorphism, noone's denying that.  But what you
demonstrated above is not it.  To get polymorphism in C++, you use
"virtual".

> The latter reference points out that C++ provides genuine Polymorphism without
> using objects at all:

>     void IsaT1( T1 a ){ return 1; }
>     void IsaT1( T2 b ){ return 0; }

presumably you meant for these to return int, not void.

>     IsaT1( x );    // returns 0 or 1 depending on arg type

Only it /doesn't/ depend on the arg type.  It depends on the static
declarations.  That's not the same thing.  Try this:

  class A {};
  class B : public A {};
  class C : public A {};

  int isaB(A x) {return 0;}
  int isaB(B x) {return 1;}

  int buggy(A x) {return isaB(x);}

now create a B instance and C instance and try calling isaB() on
them.  That works OK.  Call buggy() on them.  That fails.

Feel free to rewrite it with pointers, if you still believe that will
help.  It won't.  [And you can't even fix that with "virtual"]

That's the difference between C++ overloading and proper multiple
dispatch, as in Lisp.  Overloading is just a syntactic hack.  Writing

  X foo(A x) {...}
  Y foo(B x) {...}

is really no different than writing

  X fooA(A x) {...}
  X fooB(B x) {...}

with different names for each overloaded definition.


> This particularly useful form of Polymorphism is sorely lacking in Python.  E..g,
> picture a vector class and you want to define operators + and * for vector addition
> and for vector multiply.  So far so good.  But then you want also to define + and *
> for vector-scalar operations.  In Python you're hosed as there is no way to
> discriminate class functions by argument signature.  I hope that eventually
> changes.

I hope it doesn't.  At least, not unless it changes toward multiple
dispatch (which is unlikely).

You can do it in Python in two ways: by naming the functions
differently (which is what C++ does, under the covers), or by looking
at the types of your arguments and redispatching to the appropriate
code [with the Python equivalent of a "switch" statement or using what
the patterns people call the "visitor" pattern]

>> And just what do you think your C++ vtable lookup is?

>> You've failed to demonstrate that.  You had to resort to late binding,
>> via `virtual', to get polymorphism in C++

> A vtable is a constant array of pointers to virtual functions.

Yes.  Just like Python's dict.  [In fact, if you have a quick look at
the Python source code, you'll find that most of the built-in
functions are in fact implemented by having pointers at fixed
locations in the C struct that represents the class, too!]

>                                                            All instances of
> classes in a class hierarchy have a constant pointer to the table.  Since the
> expensive task mapping names to addresses is resolved at link time, I and most
> people fairly consider it "early binding".

If you do, you're wrong.

>                                     FWIW, I use early or late binding to
> characterize languages as a whole, not individual language features.

Well that's pretty silly.  It's not a characterisation of a language.
You have late binding in C++, via virtual, and a Python compiler
designed for delivery rather than development would be perfectly
justified in using early binding for everything possible, when it
knows you won't be redefining anything later.

> "Early binding" generally means resolved at or before link time.  The target
> function's address is decided at link time and which function gets called is
> entirely determined in advance, depending on the instance object's ID.

Yes.  Which is not the case when you use "virtual" -- it has to look
up the target function's address in the vtable at run-time.

> "Late binding" generally means resolved at runtime.  E.g., the same function name
> on the same object may refer to completely different definitions from one call to
> the next, a feature lacking in Early Binding languages.  All symbol table info
> typically is available, thus you can eval() and dir() to your hearts content.

The presence or absence of symbol table information is quite
irrelevant.  If you compile C++ with debugging flags, you get to keep
the symbol table, but you still get early binding for non-virtual
functions; if you use "virtual", you still get late binding,
regardless of whether or not you strip the symbol table.


> If you don't agree on my definition of early vs. late and my classificaiton of
> vtable lookup as an early binding scenario

Which I don't.  It's important that you use the same definitions as
everybody else, not just make up your own as you're doing here.

-- 
For þæm se þe his cræft forlætt, se bið fram þæm cræfte forlæten.
                                                                -- Ælfric
(setq reply-to
  (concatenate 'string "Paul Foley " "<mycroft" '(#\@) "actrix.gen.nz>"))



More information about the Python-list mailing list