More On Multiple Inheritance

Matthew Tuck matty@box.net.au
Tue, 06 Apr 1999 21:27:36 +0930


This message got sent out accidentally before I was ready to send it, so
I'll be combining your reply to it with the parts I didn't finish.

Hans-Dieter Dreier wrote:

>> Regarding the editor calling an object for stuff like knowing how to
>> display it, how would you handle long-running and nonterminating
>> methods?
> It should be no problem to edit the source code of a method that is
> currently executing. During editing, the reference to the generated
> ...

What I meant was, that if the editor needs the result of a function to
know how to do something, then what happens if that result takes a
really long time to do or never terminates at all?  The editor is
essentially blocked waiting for the result.

> How many different implementations will be present (ie loaded) for a
> type at any given time?
> I'm not so sure whether (typically) this will be more than one.

Well that's hard to say since we haven't done it before.  I suspect it
will be multiple in about 10% - 20% of types.
 
>> Think "shorthand".
> How could this look like?

I had this rewritten at the time I realised an earlier version had got
sent out, so I'll try to include it here.

Basically I think it's not OK to restrict impls of types.  What is OK is
restricting created objects of a type.  An example of this is a singular
object, which is a combined class/object.  This restricts the creation
of objects, and in doing so, restricts impls.  How to restrict objects
while still allowing multiple objects is another matter.

So, it should be possible to restrict impls through restricting
objects.  But this shouldn't be the default.

As for the "syntactic" overhead, a shorthand can be used for a combined
type and impl.  This would have the syntactic equivalent of a class. 
The main difference is that it doesn't prevent the definition of a
further impl, since in expanded form you could see the separation.  This
is an example of a non-statement based shorthand, which I think you were
asking about in another thread.

A new impl will automatically have the type interface, and will retain
it forever.

Changes to the type are going to break the implementations of course. 
New methods are not implemented, etc.  But a type change will always
dictate an implementation rewrite, no matter than language.  The thing
we have to handle is the fact there might be other impls out there that
will break.  Essentially, we have to know whether an impl is old or
current so we can break it.  This might just be a matter of looking to
see whether the structure is changing, or it could be a matter of
changing magic numbers (although this would change every recompile which
we might not want).

Can you think of any situations where a type change would not dictate a
change in implementation?  Possibly if the type was already
overspecified, ie parameter is read-write when it only needs to be
read-only.  A change in type won't affect the impls since they are only
reading from it.  This would probably be rare enough to not bother about
though.

>> You can't do without a "linker" if you have modules.  Even if a
>> linker just merges ASTs together, rather than does fixups etc.
> OK. Maybe my approach would need a linker too - or actually two of
> them:A *very* small one at compile time that does no more than flip
> some references,
> a bigger one that is capable of loading a workspace and merging the
> name spaces, to be called when you "include" a workspace. This one
> would then call the compiler to generate code for the parts that have
> been loaded and were depending on parts that were not included in the
> loaded package.

Could you elaborate on this scheme.  I'm only barely understanding what
you're referring to.

>> I've needed the flexibility or get/sets before and most programmers
>> probably have too - I think we don't necessarily realise it however
>> because we're used to it not being there.  Programmers don't miss a
>> feature they've never had.
> True, but I'd at least like to be able to avoid the cost if I choose
> to.

The alternative is essentially what we have now.  To allow the
restriction to variables or functions, which results in software being
distributed that is needlessly restrictive.  It's like not having any
object-oriented features and expecting programmers to write in an
object-oriented fashion.  It's possible but it just doesn't happen.

>> Many other important language advances are going to need this too,
>> eg multiple implementations leading to dynamic dispatch, making
>> arrays an implementation of a type rather than a hardcoded feature.
>> They're all going to need optimisation.  It is just the
>> continuation of what was begun by information hiding.
> Information hiding does not have such a performance hit.

Yes it does.  Not at the language level but certainly at the
programmer's level.  There would not have been a need to introduce it if
programmers had not been tinkering into the internals of types to get
extra efficiency.  Hence, information hiding does reduce efficiency.

But it does decrease the time taken to write software.  This offsets any
time taken to compile software.  I suspect in large software it results
in less time to complete the project, and that's really what the
fragility complaint is all about.  In small software it probably
doesn't, but then small software is usually quick to compile anyway.  So
is that time saved in a compile worth it?

Furthermore, time saved can be used in improving other features of the
software, too numerous to mention here, including tweaking or replacing
algorithms and hotspots, which is where a much more dramatic effect can
be found than individual tweaks.

The techniques that require dynamic dispatch are exactly the same as
this.  Without optimisation they lower efficiency, but there are a
multitude of choices, most of which will improve the situation.

Let's try to work this thing out once and for all.  Your original
objection to this has been lost in a several huge messages and clouds of
thoughts.

Firstly, do you agree that any higher-level language is going to require
a longer compilation time because of more optimisations?  I think your
major problem is that fragility will lead to dramatically increased
compile-times, am I correct?

Let's take the situation where a type is declared that has a get/set,
yet all the eventual implementations use variables.  Consider the module
containing the type, and consider working out it's layout (at least in
part).  You have two choices.  You can make the optimisation early or
late.

If you make it early, you actually look at all modules and see that they
don't contain any impls using get/sets.  Once you notice this, you mark
that the method is a variable, and go from there.  This of course
increases fragility, but this is irrelevant since the change that broke
it - introducing a get/set impl - could not have even been done without
a recompile AFTER a rewrite.  This would seem to satisfy what you want. 
The only disadvantage over the existing languages is therefore a little
extra compilation time.

The above comment doesn't apply to open-ended systems unfortunately,
since we might not get the "we know all the impls" guarantee.  But
that's another kettle of fish with another different set of issues which
I won't consider at this moment.

If you make it late, you decrease fragility.  Therefore, compiled
versions of modules can sit around that have no optimisation or code
generation dependencies on other modules.  You could choose to then make
new inter-module optimised copies of the modules.  If you keep the old
copies around, you get the same benefits of the previous scheme, except
more disk space used and disk accesses.  However there is decreased
fragility.

These could both be chosen in different places.  In fact, a wide range
of inter-module optimisations could be chosen early or late.  If there
is no inter-module optimisation, late means not at all.  However, this
is all on the proviso that we can easily determine dependencies.

There's usually no need to determine dependencies between a type and it
impl since it sits in the same syntactic entity.  Separating these makes
it a little harder, multi impls makes the situation complicated, and
prototyping would probably make it even more so.

Hence we would like to know more than just A relies on B.  Especially if
it relies on a part of B that hasn't changed, although a part of B has. 
An incremental compilation system is likely to want to do something like
this.

So if we architect the incremental compilation system right, we should
be able to handle this in a fashion that is pleasing to all.

>>> No problem, actually, since each impl must be compiled before use.
>>> It is checked then. Because it inherits from the type, it gets
>>> flagged as "need to be compiled" if the type changes significantly
>>> (i.e. visibly to the outside world).
>> Well certain situations make this impossible - impls could be
>> run-time loaded/generated/made via prototyping - or delayed - in
>> another module that hasn't been linked in yet, etc.
> I said "before use". More specifically, I meant "before execution". Of
> course no statement can be made at a time when not all neccessary
> information is present.

OK, this means whether the type method is side-effectless or not occurs
at run-time, and hence, it cannot be side-effectless at compile-time for
typing purposes.  Hence, anything that requires side-effectlessness will
not be able to use this.

>>> You'd have to change inheritance relationships then. Subtypes
>>> cannot have weaker postconditions. But I see your point. How
>>> about having the compiler flag such a condition for
>>> information purposes (as a comment), and letting the user
>>> choose whether he wants to export the condition (ie activate
>>> it for inheritance purposes).

I realised later that the extension would be to actually have the
type be side effectless if all its subtypes were, in a similar way
the type would otherwise be side-effectless if all its impls were.

What you suggest seems basically the same as emitting a warning and
letting the user fix it by being given alternatives, as we are already
discussing.

>> Anyway, the type allows "side" which in turn it grants to its
>> implementations.  The implementation may now either be marked "side"
>> or not.  If they're specified as side, yet they're side-effect less,
>> we might warn at compile-time.
> Why this? How can being side-effect less do any harm?

It means it has been granted a privelege it does not use.  This is
similar to being passed a variable you don't use, or declaring a
variable you don't use.  It's a sign that maybe you've done something
wrong.

>From Previous Message:

> In a lot of cases the compiler should know the alternatives. Eg, "insert the
> declaration or let it be". It's far less work for the user to hit a button
> than to look up the error location, find the function header where to insert
> the decl, remember (or guess) and write down the necessary type and the name.
> And in the next compile cycle correct the typo he made in that declaration if
> it was not his day. Well, for the time being this is just a vision like so
> many other things, but one that I like...

Well that's why I proposed doing this through the error message
display.  It's just a matter of clicking on the error to fix it, and all
the errors are together.

But I think I'm seeing what you mean.  You want a dialog box that comes
up to show you the bad code underneath and asked what you want to do
about it.  That sounds good.

-- 
     Matthew Tuck - Software Developer & All-Round Nice Guy
             mailto:matty@box.net.au (ICQ #8125618)
       Check out the Ultra programming language project!
              http://www.box.net.au/~matty/ultra/