[Prev][Next][Index][Thread]

Operator overloading [was: Re: Does my ideal language exist?]



[Background for those joining the thread in comp.lang.dylan ...  This
sub-thread (here newly renamed) is discussing the tensions between
operator overloading being a good thing -- because it's used in maths and
it seems to work pretty well there to simplify communication -- and a bad
thing -- because it increases the potential for programmer confusion,
particularly when user-defined types are involved and/or your language has
implicit type coercion (e.g., C, C++, Java, JavaScript, ...). 

Disclaimer: I'm a Dylan fan :-)]

On 7 Jun 2000, Peter da Silva wrote:
> In article <8hjmt4$jvq@odds.stat.purdue.edu>,
> Herman Rubin <hrubin@odds.stat.purdue.edu> wrote:
> > These languages are badly designed.  This is not the same
> > type of situation as combining various types of numbers,
> > vectors, and matrices, or even elements of more general
> > linear spaces or algebraic systems or sets.
> 
> OK, let's 'add' scalars and vectors.
> 
> 	3 + (1 2 3 4 5)
> 
> Could be: [... various plausible answers ...]
> 
> You need to take a step beyond overloading into objects, then you can
> create appropriate behaviour: 
> 
> 	3 + (accumulator 1 2 3 4 5) -> (accumulator 4 5 6 7 8)
> 	3 + (orderedlist 1 2 3 4 5) -> (orderedlist 3 1 2 3 4 5)
> 	3 + (set 1 2 3 4 5) -> (set 1 2 3 4 5)
> 
> and so on.
> 
> > >Either provide a complete object hierarchy, in whch case you gain the
> > >ability to provide type semantics (so that (3 inches) plus (10 feet) is
> > >(123 inches), and (3 inches) plus (10 pounds) is an error or raises an
> > >exception) and get something that's actually an approximation to
> > >physics or engineering, or don't even try.

Dylan does this: everything inherits from class <object>.  In addition,
there are NO implicit conversions.  (I'm pretty sure of that -- see the
Dylan Reference manual at <http://www.gwydiondylan.org/drm/drm_1.htm>.  I'm
not 100% sure how the type of numeric literals is decided -- e.g., "smallest
type that fits"? -- but they still aren't implicitly promoted etc.)

If you want a <double-float> from a <single-float> you have to convert with
the function "as".  However, you don't have to explicitly convert all the
time because functions like "\+" are defined to cover all combinations of
the built-in concrete classes.  If you try to, say, add two objects for
which no applicable method can be found, you'll get either a runtime
exception or a compile-time error, depending on when there's enough type
information.

Initially I though explicit conversion would be irritating but in (about 3
years of) practice it hasn't been.  Then again, I haven't done that much
heavily numerical programming in Dylan (and maybe I've just got used to it
:-).

Another way in Dylan to approach operators meaning different things in
different contexts is to use the renaming facility of Dylan's module system.  
Renaming is mostly just used to avoid name clashes but you can use it to
rename and hence "wrap" or outright replace standard functions (or from any
library), including the numeric operators.  The "big-integers" library in
Functional Developer (at <http://www.functionalobjects.com> -- shameless
plug ends ;-) renames various operators and classes to give you 64-bit
(well, 61-bit I think -- see below) integers.  The neat thing here is that
you only have to change a module definition in your library to import/rename
differently -- all the rest of the code "in" that module just magically uses
the new objects via the original names.

However, both <number> and <array> are abstract, open classes in Dylan, so
you probably don't need to do this for the matrix multiplication examples
quoted earlier in this thread.  (BTW, don't worry: there are concrete,
non-extensible subclasses for the typical cases, so things can stay
efficient. :-)
> > Again, this is for the user to do, not the compiler writer.
> 
> It's certainly possible to write object-oriented code in classic
> procedural languages, but I'd recommend using a language that's designed
> for the job.

In particular, in the presence of implicit coercions (user extensible via
copy constructors in C++), the language may require more effort on your part
(e.g., the "explicit" modifer for said C++ constructors) just to prevent
things you didn't intend (and probably didn't want) from happening
automatically.

You may or may not be willing to trade this "riskiness" for some other
benefits a particular language provides.  In Dylan part of the trade-off is
that the default <integer> class, for example, is only guaranteed to provide
28 bits of precision, not 32 (although that's not related directly to
overloading etc.).  In practice this is okay, too: often you just want a
loop index to be "big enough" and you can find out what the
"$maximum-integer" is; when you do need 32 bits, a particular implementation
may provide a way to get them.

> > >And, again, I have had painful experiences with overloaded operators
> > >changing the meaning of expressions in surprising and dangerous ways.

>From my experience, I can certainly sympathise in terms of numeric type
promotions/truncations etc. in C/C++.

Hugh




Follow-Ups: