From: Laurent Deniau on
Dmitry A. Kazakov wrote:
> On Tue, 21 Mar 2006 16:00:31 +0100, Laurent Deniau wrote:
> 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.

Again, it does not change. Only its type (hidden as part of its value)
changes and this is something hidden to the user (like HealthyShark and
InjuredShark are) and is managed automatically by MD. For example in
some Objective-C implementation (Smalltalk?) when you create a String,
it is never a String instance but something else with a mutating type
which changes depending on the operations you will apply to it and some
rules. String is only a king of interface (an ADT) to a cluster of
classes. An even simpler example would be Complex as an interface (ADT)
of RectComplex and PolarComplex and where toPolar(CartComplex) and
toRect(PolarComplex) would be the methods which switch the intances type.

I agree that MD + DI mix in behavior and state (in a very convenient way
to my opinion) using the fact that behavior is related to type and type
is a value, that is a state (remember classes are objects).

In usual programming paradigm, the object would include an explicit
state and all its applicable methods would check this state to select
the correct behavior, leading to dozen of nested if-then-else branches
in these methods. One might assume that nothing changes in the object
except this state (state machine).

Predicate dispatch allows the object type to be the state and MD
describes naturally the different behaviors (method specialization) like
in the slides I pointed out. No nested if-then-else are required. This
programming approach is simply a natural extension of method overriding
which can also be done by static binding and type-case tests after all.

If you have read the slides, you should have seen the code improvement
brought by combining dynamic inheritance and multimethod dynamic
dispatch. I will not try to convince you, I was only looking for
experiences and feedback on this programming approach since I am also a
newbie in it (after 15 years of C++).

> It is a misuse of type system in my eyes. To me healthy, injured, fat are
> states, not types.

The statement above just shows that combination of state and type (when
possible) is poweful.

> 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.

You are mixin different things:
order is a relation -> (generic) method
fuzzy uses discrete states -> can be types or predicates
stochastic relies on probability -> functions -> (generic) method.
once these points are identified, design is easier and less confused.

>>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.

I start to understand why you do not see the point: you don't know what
is a generic in the sens of multimethod. Let me explain a bit:

- generics are instances of the class Generic.
- methods are instances of the class Method (i.e. what is returned by
the dispatcher). Each method is attached to its generic, that is the
generic with the same name. For simplicity, you can see this relation as
a dictionnary: generic -> list of methods where name(method) ==
name(generic).
- each method instance has an implementation which corresponds to a
unique specialization of its generic.
- Methods are distinguished by their generic *and* by their specializer
(the types of the parameters).

Therefore in the above isCommutative example (which is a simple
function, not a method nor a generic), the result changes if you change
any value amongst g, c1 or c2. This is not a hack, this is how the
dispatcher works for multimethod in many language and this gives a great
flexibility.

a method invocation looks like:

c = add(a,b);

where add is the generic, a, b and c are instance of some class say A, B
and C and the method invoked is selected by taking into account the
generic add, the type of the object a that is A and the type of the
object b that is B. This method will be add(A,B) or a less specialized
version if it does not exist (see my first post about list specialization).

[...]

> Ah, but that would mean that the type changes even if the value does not.

Yes! A typical application is an IOStream. When you switch from an
OutStream to an InStream, you flush the buffer (if any) and change the
type OutStream->InStream, that's all. No need for MI, much simpler, no
burden about duplicated states or incompatible behavior. What is
*really* changing is the type, that is the set of applicable methods
(read methods instead of write methods), that is the behavior. Of
course, you can also change some fields values. What you have *really*
done is changing the behavior of an object, nothing else. But this is
one of the most difficult task in statically typed languages because in
order to have the same effect, you need a consistent state machine
manually managed in the back where DI+MMD does it naturally (see the
slides ;-) ). And think about what happen when a new state is added!

> I'm not ready to accept that.

Why?

a+, ld.
From: Laurent Deniau on
Laurent Deniau wrote:
> Dmitry A. Kazakov wrote:
>>> 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);
>>> }

One more thing about this code since I already hear your question ;-)
If you want to know if the behavior is commutative (usually the property
that we are looking for), the code above is of course incomplete and
must be extended to values, that is objects:

bool isCommutative(Generic g, object o1, object o2) {
return respondsTo(g, o1, o2) && respondsTo(g, o2, o1) &&
equal(g(o1,o2) , g(o2,o1));
}

a+, ld.
From: Dmitry A. Kazakov on
On Wed, 22 Mar 2006 15:45:05 +0100, Laurent Deniau wrote:

> Dmitry A. Kazakov wrote:
>> On Tue, 21 Mar 2006 16:00:31 +0100, Laurent Deniau wrote:
>> 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.
>
> Again, it does not change. Only its type (hidden as part of its value)
> changes and this is something hidden to the user (like HealthyShark and
> InjuredShark are) and is managed automatically by MD. For example in
> some Objective-C implementation (Smalltalk?) when you create a String,
> it is never a String instance but something else with a mutating type
> which changes depending on the operations you will apply to it and some
> rules. String is only a king of interface (an ADT) to a cluster of
> classes. An even simpler example would be Complex as an interface (ADT)
> of RectComplex and PolarComplex and where toPolar(CartComplex) and
> toRect(PolarComplex) would be the methods which switch the intances type.

If type changes what remains? In my view objects do not exist after types.
If you change the type, you change the object as well. Maybe the new object
is allocated at the same memory location and has the same bit pattern.
That's irrelevant. It is a new object of another type.

The example with Complex goes as follows. Complex'Class is the class of
complex types. PolarComplex is derived from Complex (member of the class).
RectComplex is a sibling. ToPolar can be defined as:

function ToPolar (X : PolarComplex) return RectComplex;

Note that it is not a member of Complex. There is a good reason why.
Complex knows nothing about representations and interfaces of its
descendants.

A much better design is double dispatching assignment:

function ":=" (Source : Complex) return Complex;

X : PolarComplex;
Y : RectComplex := X; -- Done!

":=" dispatches on its argument (PolarComplex) and the expected result
(RectComplex.) As simple as that!

> I agree that MD + DI mix in behavior and state (in a very convenient way
> to my opinion) using the fact that behavior is related to type and type
> is a value, that is a state (remember classes are objects).

This is bad because switching of states associated with types is the
behavior of higher order objects - classes. One should not mix them. Types
define the behavior of values. When types are themselves values, then it is
types type (a class) to define that. If you mix them, I can get nasty
problems. You can easily finish up with sets having themselves as members.

> Predicate dispatch allows the object type to be the state and MD
> describes naturally the different behaviors (method specialization) like
> in the slides I pointed out. No nested if-then-else are required. This
> programming approach is simply a natural extension of method overriding
> which can also be done by static binding and type-case tests after all.

I don't object this. I do, that one would need type mutators to make this
schemata working. One does not imply other.

>>>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.
>
> I start to understand why you do not see the point: you don't know what
> is a generic in the sens of multimethod. Let me explain a bit:

You have explained the mechanics behind polymorphic subroutines. I have no
problem with that. You can treat all polymorphic subroutines as objects of
some common type. That's no problem. But it also solves nothing. Because it
lies elsewhere. It is orthogonal to what happens with the classes c1 and
c2.

[ Potentially, one could derive some "Commutative" class from "Method",
which would describe all commutative signatures and then, somehow tell
"add" of c1 and c2 that it is from that class. Maybe it will work, maybe
not. In any case, it is not a question about types. It is about types of
types. And in any case the problem of parallel hierarchies will reappear.
You have a hierarchy of Method<--Commutative and c1<--c2<--. "add" is
present in both. If I derive
Commutative<--Commutative_With_Identity_Element and c2<-c3. Then what if c3
will have 0? ]

>> Ah, but that would mean that the type changes even if the value does not.
>
> Yes!

It is a big NO-NO to me.

> A typical application is an IOStream. When you switch from an
> OutStream to an InStream, you flush the buffer (if any) and change the
> type OutStream->InStream, that's all.

Why should I? If I needed it, I would simply make it a constraint rather
than a type.

> No need for MI, much simpler, no
> burden about duplicated states or incompatible behavior. What is
> *really* changing is the type, that is the set of applicable methods
> (read methods instead of write methods), that is the behavior. Of
> course, you can also change some fields values. What you have *really*
> done is changing the behavior of an object, nothing else. But this is
> one of the most difficult task in statically typed languages because in
> order to have the same effect, you need a consistent state machine
> manually managed in the back where DI+MMD does it naturally (see the
> slides ;-) ). And think about what happen when a new state is added!

I derive a new subtype then.

>> I'm not ready to accept that.
>
> Why?

Because I want to go in exactly the opposite direction. That is - true ADT,
which would completely abstract any representation. The goal is to have
objects which behavior is fully independent on any concrete implementation.
And if it is the type, to determine the behavior, then you simply cannot
change it, because there is nothing you can apply this change to. Neither
address, nor bit pattern, nor size is available beyond the type. Absolutely
nothing.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Laurent Deniau on
Dmitry A. Kazakov wrote:
> On Wed, 22 Mar 2006 15:45:05 +0100, Laurent Deniau wrote:
>
>
>>Dmitry A. Kazakov wrote:
>>
>>>On Tue, 21 Mar 2006 16:00:31 +0100, Laurent Deniau wrote:
>>>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.
>>
>>Again, it does not change. Only its type (hidden as part of its value)
>>changes and this is something hidden to the user (like HealthyShark and
>>InjuredShark are) and is managed automatically by MD. For example in
>>some Objective-C implementation (Smalltalk?) when you create a String,
>>it is never a String instance but something else with a mutating type
>>which changes depending on the operations you will apply to it and some
>>rules. String is only a king of interface (an ADT) to a cluster of
>>classes. An even simpler example would be Complex as an interface (ADT)
>>of RectComplex and PolarComplex and where toPolar(CartComplex) and
>>toRect(PolarComplex) would be the methods which switch the intances type.
>
>
> If type changes what remains? In my view objects do not exist after types.
> If you change the type, you change the object as well.

It is a restrictive point of view.

> Maybe the new object
> is allocated at the same memory location and has the same bit pattern.
> That's irrelevant. It is a new object of another type.

What happens if you have many "views" of this object (e.g. reference to)?

> The example with Complex goes as follows. Complex'Class is the class of
> complex types. PolarComplex is derived from Complex (member of the class).
> RectComplex is a sibling. ToPolar can be defined as:
>
> function ToPolar (X : PolarComplex) return RectComplex;

I would have suggested:

function ToPolar (X : RectComplex) return PolarComplex;

> Note that it is not a member of Complex. There is a good reason why.
> Complex knows nothing about representations and interfaces of its
> descendants.
>
> A much better design is double dispatching assignment:
>
> function ":=" (Source : Complex) return Complex;
>
> X : PolarComplex;
> Y : RectComplex := X; -- Done!
>
> ":=" dispatches on its argument (PolarComplex) and the expected result
> (RectComplex.) As simple as that!

It not that simple. What you describe is the visitor pattern known for
at least a decade, nothing new then. Everybody knows the problems solved
and inherent to this pattern. Multimethods are often seen as the
solution to the problem of this pattern. And this pattern is often used
to simulate multimethods when not supported.

>>I agree that MD + DI mix in behavior and state (in a very convenient way
>>to my opinion) using the fact that behavior is related to type and type
>>is a value, that is a state (remember classes are objects).
>
>
> This is bad because switching of states associated with types is the
> behavior of higher order objects - classes.

This is what I am talking about from the beginning. The switch of type
is an internal behavior to simulate a state machine and automatically
managed by the dispatcher instead by the programmer. Only a class can
change the type of its instances and this change has to respect some
constraints.

> One should not mix them. Types
> define the behavior of values. When types are themselves values, then it is
> types type (a class) to define that.

Also called metaclass, a powerful notion known for decades and well defined.

> If you mix them, I can get nasty
> problems. You can easily finish up with sets having themselves as members.

Not possible. Metaclasses allow either parallel hierarchy (one metaclass
per class), either a projection of hierarchy (one metaclass for many
classes). Prototype bases langages (Self, Cecil, Slate) allow even more
flexibility and complex constructions.

> Because I want to go in exactly the opposite direction. That is - true ADT,
> which would completely abstract any representation. The goal is to have
> objects which behavior is fully independent on any concrete
> implementation.

In dynamic typing languages, all instances are ADTs. The user only sees
methods and knows nothing about the representation or even the type of
the objects including classes (except if he queries for knowing it using
the appropriate method). This is fully *behavior* oriented, the topic of
my question...

May I suggest a simple thing? Write a formal implementation of the
algorithm described in the slides I cited and let see if it is closer to
what is shown p5, p8 or p15. Then we will see what needs to be modified
to add a new type of Shark, say a BigShark with its states Healthy and
Injured.

a+, ld.
From: Dmitry A. Kazakov on
On Fri, 24 Mar 2006 17:39:30 +0100, Laurent Deniau wrote:

> Dmitry A. Kazakov wrote:
>> On Wed, 22 Mar 2006 15:45:05 +0100, Laurent Deniau wrote:
>>
>>>Dmitry A. Kazakov wrote:
>>>
>> Maybe the new object
>> is allocated at the same memory location and has the same bit pattern.
>> That's irrelevant. It is a new object of another type.
>
> What happens if you have many "views" of this object (e.g. reference to)?

Depends on what you mean under reference. When reference is an object (like
pointer), then it is not a view, but a distinct object of different type.
When reference means "by-reference", then it is an implementation detail.
When you mean a partial view like "const", then it is a constrained type.

In my world, each time you switch the type, even if that means just putting
a constraint on it, you change the object. This types conversion might be
an identity (null) operation, but it is present. Otherwise, one should
consider multiple types of the same object, which is admittedly doable, but
represents a sufficiently more complex model, which advantages are not
obvious to me.

>> The example with Complex goes as follows. Complex'Class is the class of
>> complex types. PolarComplex is derived from Complex (member of the class).
>> RectComplex is a sibling. ToPolar can be defined as:
>>
>> function ToPolar (X : PolarComplex) return RectComplex;
>
> I would have suggested:
>
> function ToPolar (X : RectComplex) return PolarComplex;

Yes, of course. It was a typo error on my side.

>> Note that it is not a member of Complex. There is a good reason why.
>> Complex knows nothing about representations and interfaces of its
>> descendants.
>>
>> A much better design is double dispatching assignment:
>>
>> function ":=" (Source : Complex) return Complex;
>>
>> X : PolarComplex;
>> Y : RectComplex := X; -- Done!
>>
>> ":=" dispatches on its argument (PolarComplex) and the expected result
>> (RectComplex.) As simple as that!
>
> It not that simple. What you describe is the visitor pattern known for
> at least a decade, nothing new then. Everybody knows the problems solved
> and inherent to this pattern. Multimethods are often seen as the
> solution to the problem of this pattern. And this pattern is often used
> to simulate multimethods when not supported.

So we could agree upon. That multimethod would solve this particular case
without mutating types?

>>>I agree that MD + DI mix in behavior and state (in a very convenient way
>>>to my opinion) using the fact that behavior is related to type and type
>>>is a value, that is a state (remember classes are objects).
>>
>> This is bad because switching of states associated with types is the
>> behavior of higher order objects - classes.
>
> This is what I am talking about from the beginning. The switch of type
> is an internal behavior to simulate a state machine and automatically
> managed by the dispatcher instead by the programmer.

I fully agree with that. Dispatching changes the type from the class
(polymorphic) to a specific type (non-polymorphic.) This is why I think
that C++'s re-dispatch in methods was a big mistake.

But in my view dispatch clearly changes the object. This is very important
for handling the cases when objects are small. So changing the type of,
say, polymorphic Boolean'Class might mean stripping away the type tag and
producing exactly one bit Boolean. Note that this by no means would require
mutating types. Even for in-out parameters: you create a new object pass it
down to the method and then convert the result back.

> Only a class can
> change the type of its instances and this change has to respect some
> constraints.

You can simplify it by melting the constraints with the class and saying
that the only constraint is that the result is in the class.

>> Because I want to go in exactly the opposite direction. That is - true ADT,
>> which would completely abstract any representation. The goal is to have
> > objects which behavior is fully independent on any concrete
> > implementation.
>
> In dynamic typing languages, all instances are ADTs. The user only sees
> methods and knows nothing about the representation or even the type of
> the objects including classes (except if he queries for knowing it using
> the appropriate method). This is fully *behavior* oriented, the topic of
> my question...

Then I don't see why you insist on changing behavior of something that
consists only of the behavior. You just cannot do that, because there is no
way to mark the rest. You need some method to identify the object. But
identification by a method is also a behavior. No way.

> May I suggest a simple thing? Write a formal implementation of the
> algorithm described in the slides I cited and let see if it is closer to
> what is shown p5, p8 or p15. Then we will see what needs to be modified
> to add a new type of Shark, say a BigShark with its states Healthy and
> Injured.

Ah, it is difficult, because I am not convinced that MD is needed for
Encounter. The behavior of primitive animals does not vary that much. One
estimates another and then tries to attack, ignore or flee. Depending on
the intent of both, the result is an attack, hunt, ignoring, fight,
dispersing or fleeing. So this is class-wide, i.e. all descendants of Fish
share the behavior in Encounter:

type Estimation is (Weaker, Same, Stronger);

type Fish is ...;
function Estimate (Self : X; Other : Y) return Estimation;
procedure Attack (Predator, Prey : in out Fish);
procedure Hunt (Predator, Prey : in out Fish);
procedure Ignore (One, Another : in out Fish);
procedure Fight (One, Another : in out Fish);
procedure Disperse (One, Another : in out Fish);
procedure Flee (Standing, Running : in out Fish);

procedure Encounter (X, Y : in out Fish'Class) is
begin
case Estimate (X, Y) is
when Weaker =>
case Estimate (Y, X) is
when Weaker => Disperse (Y, X);
when Same => Flee (Y, X);
when Stronger => Hunt (Y, X);
end case;
when Same =>
case Estimate (Y, X) is
when Weaker => Flee (X, Y);
when Same => Ignore (X, Y);
when Stronger => Attack (Y, X);
end case;
when Stronger =>
case Estimate (Y, X) is
when Weaker => Hunt (X, Y);
when Same => Attack (X, Y);
when Stronger => Fight (X, Y);
end case;
end case;
end Encounter;

A more realistic model would be repetitive state modification and
undertaking actions depending on the fish's state and the state of the
environment.

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