From: Dmitry A. Kazakov on
On Mon, 20 Mar 2006 11:00:56 +0100, Laurent Deniau wrote:

> No. It is the ability to change the class (and the type) of an object at
> runtime. It is usually supported by language with dynamic typing or
> Prototype language both with dynamic dispatch and it can be simulated by
> language with predicate dispatch. The only languages I am aware of which
> support static type checking and dynamic multiple inheritance (but with
> some constraints) are the gbeta and caesarJ languages, see the papers
> "Safe dynamic multiple inheritance" and "A virtual class calculus" from
> E. Ernst for more information.

I don't see use cases for changing the class. One could allow changing the
specific type. I.e. let you have a polymorphic object, which consists of
the type tag and the corresponding specific object. You could change the
tag and replace the specific object. The following is legal Ada:

X : T1; -- T1 is derived from T
Y : T1; -- T1 is derived from T
Z : T2; -- T2 is derived from T
Chameleon : T'Class := X;
begin
Chameleon := Y; -- This is OK
Chameleon := Z; -- Raises Constraint_Error

Ada does not allow changing type tags, but this is not a compile-time
constraint!

BTW, there are good arguments for not changing the type tags (stack memory
allocation issues.) Then with a really good ADT one could achieve the same
effect using safe referential types (handles.)

>> Anyway, I don't see how to avoid "diamond diagram" if not by shifting all
>> the burden to the programmer.
>
> This is exactly my question except that I do not have examples (in this
> context: SI + dynamic multimethod + dynamic inheritance) where MI is
> required ;-)

IMO, there is no any. For mutating classes one should just use Any'Class.
Mutating types are not so obvious, but transparent handles could do the
job.

>> IMO, it is like multiple dispatch, one can
>> emulate it using SI, and have countless problems with implementing quite
>> idiotic patterns.
>
> Yes but in this case, you have a problem solved by O(2N^P) methods
> instead of O(N*P) multimethods, where N is the number of the classes
> overriding the less specialized multimethod and P the number of
> specializer of the multimethod. Looks like something hard to do by hand
> with SI when N > 2 and P >= 2.

Geometrical explosion is not the only problem, IMO, unknown siblings seem
worse.

>> From a bird's perspective, either MI should have a feasible and efficient
>> implementation, or there must be something badly wrong with the whole
>> concept of inheritance, subtyping, subclassing etc. I don't believe in the
>> latter.
>
> Implementing MI *is* feasible with good efficiency (both size and speed)
> otherwise I would not ask my question ;-) But when you want to implement
> MI as aggregation with nearly the efficiency of SI then you start to
> touch the state of the art of MI implementation, specially when
> implementing object construction and destruction, "virtual" method
> invocation and C++ like dynamic_cast operator.

Ah, construction and destruction is complete rubbish in all languages I
know! (:-)) This is yet another unsolved problem: extensible polymorphic
methods.

In my view one cannot solve construction/destruction controversy without
separation classes and types. If you have it, then you can have distinct
class and type constructors. Class constructors could then take care of MI
components, and dispatch within the body.

I think that T != T'Class is the crux of types systems yet to come.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Laurent Deniau on
Dmitry A. Kazakov wrote:
> On Mon, 20 Mar 2006 11:00:56 +0100, Laurent Deniau wrote:
>
>
>>No. It is the ability to change the class (and the type) of an object at
>>runtime. It is usually supported by language with dynamic typing or
>>Prototype language both with dynamic dispatch and it can be simulated by
>>language with predicate dispatch. The only languages I am aware of which
>>support static type checking and dynamic multiple inheritance (but with
>>some constraints) are the gbeta and caesarJ languages, see the papers
>>"Safe dynamic multiple inheritance" and "A virtual class calculus" from
>>E. Ernst for more information.
>
>
> I don't see use cases for changing the class. One could allow changing the
> specific type. I.e. let you have a polymorphic object, which consists of
> the type tag and the corresponding specific object. You could change the
> tag and replace the specific object. The following is legal Ada:
>
> X : T1; -- T1 is derived from T
> Y : T1; -- T1 is derived from T
> Z : T2; -- T2 is derived from T
> Chameleon : T'Class := X;
> begin
> Chameleon := Y; -- This is OK
> Chameleon := Z; -- Raises Constraint_Error
>
> Ada does not allow changing type tags, but this is not a compile-time
> constraint!
>
> BTW, there are good arguments for not changing the type tags (stack memory
> allocation issues.) Then with a really good ADT one could achieve the same
> effect using safe referential types (handles.)

But dynamic inheritance (or predicate dispatch) allows a lot of
simplification when algorithms and/or objects use states to drive their
behavior. As a simple example, you can read the slides (very short and
easy):

http://tunes.org/~eihrul/talk.pdf

This is a Slate example about the multimethod dispatch with protypes,
something very close to what I do.

Another example with MI is shown in the Cecil documentation, p22:

http://www.cs.washington.edu/research/projects/cecil/www/Release/doc-cecil-lang/cecil-spec.ps.gz

>>>Anyway, I don't see how to avoid "diamond diagram" if not by shifting all
>>>the burden to the programmer.
>>
>>This is exactly my question except that I do not have examples (in this
>>context: SI + dynamic multimethod + dynamic inheritance) where MI is
>>required ;-)
>
>
> IMO, there is no any. For mutating classes one should just use Any'Class.
> Mutating types are not so obvious, but transparent handles could do the
> job.

But handles + multimethod bring us back to a geometric complexity behind
the scene and/or problems with unforeseen extensions.

>>>IMO, it is like multiple dispatch, one can
>>>emulate it using SI, and have countless problems with implementing quite
>>>idiotic patterns.
>>
>>Yes but in this case, you have a problem solved by O(2N^P) methods
>>instead of O(N*P) multimethods, where N is the number of the classes
>>overriding the less specialized multimethod and P the number of
>>specializer of the multimethod. Looks like something hard to do by hand
>>with SI when N > 2 and P >= 2.
>
>
> Geometrical explosion is not the only problem, IMO, unknown siblings seem
> worse.

I agree, this is why I prefer SI+multimethods than MI+single_methods,
the former being even simpler than the later to implement.

a+, ld.
From: Dmitry A. Kazakov on
On Mon, 20 Mar 2006 19:11:28 +0100, Laurent Deniau wrote:

> Dmitry A. Kazakov wrote:
>> On Mon, 20 Mar 2006 11:00:56 +0100, Laurent Deniau wrote:
>>
>>>No. It is the ability to change the class (and the type) of an object at
>>>runtime. It is usually supported by language with dynamic typing or
>>>Prototype language both with dynamic dispatch and it can be simulated by
>>>language with predicate dispatch. The only languages I am aware of which
>>>support static type checking and dynamic multiple inheritance (but with
>>>some constraints) are the gbeta and caesarJ languages, see the papers
>>>"Safe dynamic multiple inheritance" and "A virtual class calculus" from
>>>E. Ernst for more information.
>>
>> I don't see use cases for changing the class. One could allow changing the
>> specific type. I.e. let you have a polymorphic object, which consists of
>> the type tag and the corresponding specific object. You could change the
>> tag and replace the specific object. The following is legal Ada:
>>
>> X : T1; -- T1 is derived from T
>> Y : T1; -- T1 is derived from T
>> Z : T2; -- T2 is derived from T
>> Chameleon : T'Class := X;
>> begin
>> Chameleon := Y; -- This is OK
>> Chameleon := Z; -- Raises Constraint_Error
>>
>> Ada does not allow changing type tags, but this is not a compile-time
>> constraint!
>>
>> BTW, there are good arguments for not changing the type tags (stack memory
>> allocation issues.) Then with a really good ADT one could achieve the same
>> effect using safe referential types (handles.)
>
> But dynamic inheritance (or predicate dispatch) allows a lot of
> simplification when algorithms and/or objects use states to drive their
> behavior. As a simple example, you can read the slides (very short and
> easy):
>
> http://tunes.org/~eihrul/talk.pdf
>
> This is a Slate example about the multimethod dispatch with protypes,
> something very close to what I do.

I still don't see how this could justify need in mutating types. In the
example given, in all its roles a fish remains fish. It is clear that

Encounter(X : in out Fish; Y : in out Fish);

must be a multimethod. But the effect of this method does not change the
types of any objects involved. When X eats Y both remain in the class of
fishes. Their specific types stay constant as well. Even if the prey were
contaminated with radioactive elements, that wouldn't cause the predator to
become, say, a star ship, in the next generation, maybe. (:-))

>>>>Anyway, I don't see how to avoid "diamond diagram" if not by shifting all
>>>>the burden to the programmer.
>>>
>>>This is exactly my question except that I do not have examples (in this
>>>context: SI + dynamic multimethod + dynamic inheritance) where MI is
>>>required ;-)
>>
>> IMO, there is no any. For mutating classes one should just use Any'Class.
>> Mutating types are not so obvious, but transparent handles could do the
>> job.
>
> But handles + multimethod bring us back to a geometric complexity behind
> the scene and/or problems with unforeseen extensions.

Right. Handles as subtypes of the objects they point to, are only to solve
the problem of mutating types. They don't solve the problem of multiple
dispatch, which I see as an independent problem.

The signature matching rules don't solve that problem either. They reduce
the space of choices by projecting it onto another space of lesser
dimension. For example, delegation presumes that the space is decomposable
to a Cartesian product. But, alas, there is no such projection which would
fit for all possible scenarios. So it is a fundamentally flawed way, IMO.

A right one, could be to allow the programmer to specify a-priory known [=
from the application domain] groups of symmetry and let the compiler to use
them to reduce the number of variants. For example, I'd like to be able to
express things like: "this operation is commutative."

In the example with fishes, you have a parallel hierarchy of prey-predator
relation. The problem there is, IMO, not in MD, but in an inability to
express and handle parallel types hierarchies. This is tightly related to
MI and interfaces. Shark is a fish, it is a predator and it also is a prey.
But it is not so that it becomes not a shark when you hunt it. It is still
a shark. So take care, it can bite you!

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Laurent Deniau on
Dmitry A. Kazakov wrote:
> On Mon, 20 Mar 2006 19:11:28 +0100, Laurent Deniau wrote:
> I still don't see how this could justify need in mutating types. In the
> example given, in all its roles a fish remains fish. It is clear that
>
> Encounter(X : in out Fish; Y : in out Fish);
>
> must be a multimethod.

Yes.

> But the effect of this method does not change the
> types of any objects involved.

Yes it does.

> When X eats Y both remain in the class of
> fishes. Their specific types stay constant as well.

Not in the example. Shark is a specialization of (derived from) Fish and
HealthyShark and InjuredShark are specializations of (derived from)
Shark. Therefore in MD:

Encounter(X : in out Shark; Y : in out Fish);

- is also a multimethod refering to the same generic method
- overrides Encounter(Fish; Fish); (differs from C++)
- has a different behavior from Encounter(Fish; Fish);

Same for Encounter(HealthyShark; Fish) vs Encounter(Shark; Fish) and
Encounter(HealthyShark; Shark) vs Encounter(HealthyShark; Fish), the
latter being the most specialized override of Encounter(Fish; Fish) of
this example.

In Encounter(HealthyShark; Shark), the type of X will switch from an
HealthyShark to an InjuredShark. The type of X is used to modelize a
*state* change of X and this is call predicate dispatch or sometimes
(more restrictive) dynamic inheritance. Then instead of having all the
methods of Shark (or even Fish which may not be aware of this state)
checking whether X is Healthy or not, the MD does the work automatically
and no more code is required. This is one of the most flexible pattern I
know and it is extensively use in class' clusters design.

This makes the algorithm of slide 15 much simpler than its original
version slide 5.

> A right one, could be to allow the programmer to specify a-priory known [=
> from the application domain] groups of symmetry and let the compiler to use
> them to reduce the number of variants. For example, I'd like to be able to
> express things like: "this operation is commutative."

This is not possible since it is related to a combination of the
operation plus an ordered set of parameter types. This is one of the
problems of C++ templates that concept checking tries to solve (see C++
n1886). That is specializing properties of operations for a set of types
without describing all the variant. Otherwise complex types hierarchies,
intermediate template functions and template specializations must be
produced to achieve the same result (see boost concept).

On the side of MD, you can use something like

if (respondsTo(generic, classA, classB) &&
respondsTo(generic, classB, classA)) {
code assuming the method specialization of the generic is commutative
for classA and classB
}

note that generic, classA and classB are polymorphic types which allows
to write a generic version of say isCommutative

bool isCommutative(Generic g, Class c1, Class c2) {
return respondsTo(g, c1, c2) && respondsTo(g, c2, c1);
}

> In the example with fishes, you have a parallel hierarchy of prey-predator
> relation. The problem there is, IMO, not in MD, but in an inability to
> express and handle parallel types hierarchies. This is tightly related to
> MI and interfaces. Shark is a fish, it is a predator and it also is a prey.

No, this is related to relation between types. A Shark is a Fish in any
context but it is a Predator only for some other types and a Prey for
say the complementary set of applicable types. IMHO, this has nothing to
do with MI nor interfaces except that the latter can simulate the
behavior in a very inflexible way. This where MD is very useful.

a+, ld.
From: Dmitry A. Kazakov on
On Tue, 21 Mar 2006 16:00:31 +0100, Laurent Deniau wrote:

> Dmitry A. Kazakov wrote:
>> On Mon, 20 Mar 2006 19:11:28 +0100, Laurent Deniau wrote:
>> I still don't see how this could justify need in mutating types. In the
>> example given, in all its roles a fish remains fish. It is clear that
>>
>> Encounter(X : in out Fish; Y : in out Fish);
>>
>> must be a multimethod.
>
> Yes.
>
>> But the effect of this method does not change the
>> types of any objects involved.
>
> Yes it does.
>
>> When X eats Y both remain in the class of
>> fishes. Their specific types stay constant as well.
>
> Not in the example. Shark is a specialization of (derived from) Fish and
> HealthyShark and InjuredShark are specializations of (derived from)
> Shark. Therefore in MD:
>
> Encounter(X : in out Shark; Y : in out Fish);
>
> - is also a multimethod refering to the same generic method
> - overrides Encounter(Fish; Fish); (differs from C++)
> - has a different behavior from Encounter(Fish; Fish);

I don't understand this. Encounter is polymorphic in both parameters. This
precisely means, that it is defined on each combination of specific types
T1 x T2, derived from Fish. Because Shark is in Fish'Class this of course
includes

Encounter(X : in out Shark; Y : in out Fish);

Which is in my view just an overriding. It is not a separate method but an
implementation of

Encounter(X : in out Fish; Y : in out Fish);

for Shark x Fish.

> In Encounter(HealthyShark; Shark), the type of X will switch from an
> HealthyShark to an InjuredShark. The type of X is used to modelize a
> *state* change of X and this is call predicate dispatch or sometimes
> (more restrictive) dynamic inheritance.

I see. But this is a different story. You want to dispatch to Shark and
then to make something different out of it. The question is how different
that might be. Will it be a Shark, a Fish etc. You can describe that
without type mutators:

Encounter
( X : in Fish;
Y : in Fish;
NewX out Fish'Class; -- It cannot become a star ship
NewY out Fish'Class
);

Now Encounter dispatches on two original objects and creates two new. It is
just a double abstract factory.

Though I doubt that an InjuredShark is a distinct type of species.

> Then instead of having all the
> methods of Shark (or even Fish which may not be aware of this state)
> checking whether X is Healthy or not, the MD does the work automatically
> and no more code is required. This is one of the most flexible pattern I
> know and it is extensively use in class' clusters design.

It is a misuse of type system in my eyes. To me healthy, injured, fat are
states, not types. Moreover, there are definitely shades of healthiness,
which is not discrete, it is even not ordered, and might be fuzzy or
stochastic. I wouldn't map it onto types. Unless you'd introduce
indiscrete, non-deterministic type system [that might turn an interesting
speculation.]

My rule of thumb is: anything that can be a value must be. Only if it
cannot, then consider it being a type. If that is impossible, consider it
class. Things sufficiently grow in complexity as we ascend the ladder:
value->type->class->types set->...

> This makes the algorithm of slide 15 much simpler than its original
> version slide 5.

>> A right one, could be to allow the programmer to specify a-priory known [=
>> from the application domain] groups of symmetry and let the compiler to use
>> them to reduce the number of variants. For example, I'd like to be able to
>> express things like: "this operation is commutative."
>
> This is not possible since it is related to a combination of the
> operation plus an ordered set of parameter types. This is one of the
> problems of C++ templates that concept checking tries to solve (see C++
> n1886). That is specializing properties of operations for a set of types
> without describing all the variant. Otherwise complex types hierarchies,
> intermediate template functions and template specializations must be
> produced to achieve the same result (see boost concept).
>
> On the side of MD, you can use something like
>
> if (respondsTo(generic, classA, classB) &&
> respondsTo(generic, classB, classA)) {
> code assuming the method specialization of the generic is commutative
> for classA and classB
> }
>
> note that generic, classA and classB are polymorphic types which allows
> to write a generic version of say isCommutative
>
> bool isCommutative(Generic g, Class c1, Class c2) {
> return respondsTo(g, c1, c2) && respondsTo(g, c2, c1);
> }

It is still not what is needed. First it has an explicit parameter
"generic" which deals with the tags of the parameters. Secondly it does not
dispatches on c1 and c2, but on g. It is looks as a nasty hack. Basically,
what is needed is to define implementations for some combination of types
through implementation for other combination on the whole class. For
example, in some imaginary syntax, a symmetric relation could be described
as:

function Symmetric (C1, C2 : T) return Boolean
when C1'Tag < C2'Tag or C1'Tag = C2'Tag is abstract;
-- To be overridden in all descendants

function Symmetric (C1, C2 : T) return Boolean
when C2'Tag <: C1'Tag is
begin
return Symmetric (C2, C1);
end Symmetric;
-- To be inherited by all descendants

Here "when" clause filters out some variants, namely ones where the first
parameter is closer to the root of the class. C'Tag denotes the object type
tag. The compiler is required to check that the union of all "when"
statically yields true.

>> In the example with fishes, you have a parallel hierarchy of prey-predator
>> relation. The problem there is, IMO, not in MD, but in an inability to
>> express and handle parallel types hierarchies. This is tightly related to
>> MI and interfaces. Shark is a fish, it is a predator and it also is a prey.
>
> No, this is related to relation between types. A Shark is a Fish in any
> context but it is a Predator only for some other types and a Prey for
> say the complementary set of applicable types.

Ah, but that would mean that the type changes even if the value does not.
I'm not ready to accept that.

> IMHO, this has nothing to
> do with MI nor interfaces except that the latter can simulate the
> behavior in a very inflexible way. This where MD is very useful.

Yes it is, but only to save types. MD moves the issue of "predator hunts
prey" from individual objects to the tuples of objects. If Hunt is
associated with one type then there indeed mutating objects could appear.
But if it is with a tuple then whether shark is a prey would depend on
Hunt, not on Shark. MI is here to mix prey->predator pyramid with species
hierarchy.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de