comp.lang.ada
 help / color / mirror / Atom feed
* Class-wide types algebra
@ 2016-09-12 20:26 Dmitry A. Kazakov
  2016-09-12 21:12 ` rieachus
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-12 20:26 UTC (permalink / raw)


Presently class-wide types are singletons. I would propose to extend 
that to call-wide expressions, namely conjunction in all places where 
T'Class is expected. e.g.

    procedure Foo (X : in out T'Class and R'Class); -- Not Ada

    task type Worker (X : not null access T'Class and R'Class) is

    if X is in (T'Class and R'Class) then

    Object : T'Class and R'Class renames (T'Class and R'Class) (X);

It should not be difficult, at first glance.

Rationale. Presently it is impossible to declare an operation requiring 
an instance derived from the type T that also implements interface R. We 
cannot do that without having a explicit root type

    type T_And_R is new T and R with ...;

But that will exclude existing types already implementing T. If we 
already have:

    type S is new T with ...;

A type derived from S with R added would not fall into T_And_R.

Generics do not help much because descendants of T may have 
discriminants which are all unknown in advance, so there is no chance to 
create instances derived types.

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

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-12 20:26 Class-wide types algebra Dmitry A. Kazakov
@ 2016-09-12 21:12 ` rieachus
  2016-09-12 21:39   ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: rieachus @ 2016-09-12 21:12 UTC (permalink / raw)


On Monday, September 12, 2016 at 4:26:24 PM UTC-4, Dmitry A. Kazakov wrote:
> Presently class-wide types are singletons. I would propose to extend 
> that to call-wide expressions, namely conjunction in all places where 
> T'Class is expected. e.g.
> 
The problem may be worth solving, but I dread the number of cases that would have to be considered to come up with workable rules.  A more restricted case, which should deal with the issue would be to allow "T'Class is in R'Class;" where R is an interface, or an ancestor of T'Class.

Why the second case?  The real issue seems to be visibility.  You have an interface, and a type which could/should be a member, but the declaration is more deeply nested.  (There would still need to be some magic so that the lifetime of T'Class is the same as R'Class.  Or better, just a rule that requires them to have the same lifetime from other rules.)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-12 21:12 ` rieachus
@ 2016-09-12 21:39   ` Dmitry A. Kazakov
  2016-09-13 11:46     ` rieachus
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-12 21:39 UTC (permalink / raw)


On 2016-09-12 23:12, rieachus@comcast.net wrote:
> On Monday, September 12, 2016 at 4:26:24 PM UTC-4, Dmitry A. Kazakov wrote:
>> Presently class-wide types are singletons. I would propose to extend
>> that to call-wide expressions, namely conjunction in all places where
>> T'Class is expected. e.g.
>>
> A more
> restricted case, which should deal with the issue would be to allow
> "T'Class is in R'Class;" where R is an interface, or an ancestor of T'Class.

I am not sure what you mean. Ada's "in" has the mathematical meaning 
"member of" (∈). If you meant "subset of"/"subsumption" (⊆) then it is 
just same as

    T in R'Class

It is already there. It would be nice to have comparisons like:

    T'Class <= R'Class  <=>  T in R'Class

But that is not the problem.

> Why the second case? The real issue seems to be visibility. You have
> an interface, and a type which could/should be a member, but the
> declaration is more deeply nested. (There would still need to be some
> magic so that the lifetime of T'Class is the same as R'Class. Or better,
> just a rule that requires them to have the same lifetime from other rules.)

I don't see any problems with that. All types in question are statically 
known. Type tests are expressible already

    X in (T'Class and R'Class)  <=>  (X in T'Class) and (X in R'Class)

The real problem is declarations not tests.

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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-12 21:39   ` Dmitry A. Kazakov
@ 2016-09-13 11:46     ` rieachus
  2016-09-13 12:26       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: rieachus @ 2016-09-13 11:46 UTC (permalink / raw)


On Monday, September 12, 2016 at 5:39:32 PM UTC-4, Dmitry A. Kazakov wrote:
> On 2016-09-12 23:12, rieachus@comcast.net wrote:
> 
> I am not sure what you mean. Ada's "in" has the mathematical meaning 
> "member of" (∈). If you meant "subset of"/"subsumption" (⊆) then it is 
> just same as
> 
>     T in R'Class

I was using "in" as syntactic sugar, extending R'Class without creating a new (named) subclass.  As unrelated to the Boolean version as to its use in subprogram declarations.

> > Why the second case? The real issue seems to be visibility. You have
> > an interface, and a type which could/should be a member, but the
> > declaration is more deeply nested. (There would still need to be some
> > magic so that the lifetime of T'Class is the same as R'Class. Or better,
> > just a rule that requires them to have the same lifetime from other rules.)
> 
> I don't see any problems with that. All types in question are statically 
> known. Type tests are expressible already
> 
>     X in (T'Class and R'Class)  <=>  (X in T'Class) and (X in R'Class)
> 
> The real problem is declarations not tests.

IMHO the biggest problem in Ada is the namespace pollution of types.  Dotted notation is acceptable when package names hide each other, but I hate having to write "subtype Foo is Bar.Foo."  Subprograms can be overloaded, but adding a (new) type name during maintenance can break something down a chain of derivations.

A long time ago, Lori Clarke and others wrote: "Nesting in Ada is for the Birds."  http://dl.acm.org/citation.cfm?id=948651 There are a few missing pieces when you go to mix class wide types into Ada.  I've started numbering the often never mentioned again types that appear when using interfaces to do mix-ins.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-13 11:46     ` rieachus
@ 2016-09-13 12:26       ` Dmitry A. Kazakov
  2016-09-28  0:05         ` Randy Brukardt
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-13 12:26 UTC (permalink / raw)


On 13/09/2016 13:46, rieachus@comcast.net wrote:

> IMHO the biggest problem in Ada is the namespace pollution of types.

That is not a problem either because resulted types are anonymous.

> Dotted notation is acceptable when package names hide each other,
> but  I hate having to write "subtype Foo is Bar.Foo."

Well that is another language problem that types cannot orderly be 
renamed. But I don't see how it is related to class-wide types. And type 
renaming is only a problem in generics which are beyond any hope anyway.

> Subprograms can be
> overloaded, but adding a (new) type name during maintenance can break
> something down a chain of derivations.

No, it cannot break anything, it is just like "access T", T'Base etc.

> A long time ago, Lori Clarke and others wrote: "Nesting in Ada is
> for  the Birds." http://dl.acm.org/citation.cfm?id=948651 There are a few
> missing pieces when you go to mix class wide types into Ada. I've
> started numbering the often never mentioned again types that appear when
> using interfaces to do mix-ins.

Yes, I have long proposed to introduce T'Interface to denote the type 
interface stripped of implementation. That will spare you many explicit 
interface declarations and fix language library problems. E.g.

    type Root_Stream_Type is ...

OK, that was a mistake, it should have been

    type Root_Stream_Interface is limited interface;
    type Root_Stream_Type is abstract new Root_Stream_Interface
       with private;

With T'Interface you could still work it around:

    type DB_Stream is new Data_Base_Connector -- I need my base type here
       and Root_Stream_Type'Interfrace; -- But it is a stream still

[Of course T'Interface will override abstract all non-abstract not null 
primitive operations inherited by T]

Of course:

T'Immediate_Parent to denote the parent type.
T'Element to denote array element type
T'Index to denote array index type
T'Member(Name/Position) to denote record member type
T'Constraints to denote a null record type with constraints of T
T'Target to denote the pointer's target type

Nothing of this would be any difficult, but it would make usage of 
generics a lot easier.

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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-13 12:26       ` Dmitry A. Kazakov
@ 2016-09-28  0:05         ` Randy Brukardt
  2016-09-28  7:31           ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: Randy Brukardt @ 2016-09-28  0:05 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:nr8rah$14gp$1@gioia.aioe.org...
> ... E.g.
>
>    type Root_Stream_Type is ...
>
> OK, that was a mistake, it should have been
>
>    type Root_Stream_Interface is limited interface;
>    type Root_Stream_Type is abstract new Root_Stream_Interface
>       with private;

We would have made the above change years ago if it wasn't severely 
incompatible.

> With T'Interface you could still work it around:

But you'd still have the problem that interfaces can't be hidden (which is 
the source of the incompatibility). And lifting that restriction would be a 
nightmare (we've tried on several occasions), as you quickly get scenarios 
where you have to have two different copies of the same interface in order 
to keep sanity about the operations. (Else one has to totally abandon 
privacy, allowing hidden operations to be overridden. That leads to usage 
madness...)

The problem with all forms of MI is diamond inheritance, which get much 
worse in Ada because of our strict privacy rules. I don't think that could 
ever be solved for Ada (perhaps a new language could solve it, but not Ada).

                                         Randy.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-28  0:05         ` Randy Brukardt
@ 2016-09-28  7:31           ` Dmitry A. Kazakov
  2016-09-28 20:17             ` Randy Brukardt
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-28  7:31 UTC (permalink / raw)


On 28/09/2016 02:05, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:nr8rah$14gp$1@gioia.aioe.org...
>> ... E.g.
>>
>>    type Root_Stream_Type is ...
>>
>> OK, that was a mistake, it should have been
>>
>>    type Root_Stream_Interface is limited interface;
>>    type Root_Stream_Type is abstract new Root_Stream_Interface
>>       with private;
>
> We would have made the above change years ago if it wasn't severely
> incompatible.
>
>> With T'Interface you could still work it around:
>
> But you'd still have the problem that interfaces can't be hidden (which is
> the source of the incompatibility). And lifting that restriction would be a
> nightmare (we've tried on several occasions), as you quickly get scenarios
> where you have to have two different copies of the same interface in order
> to keep sanity about the operations. (Else one has to totally abandon
> privacy, allowing hidden operations to be overridden. That leads to usage
> madness...)

That is largely because interfaces were introduced as named types, which 
was an error as well as introducing them at all.

T'Interface would be an anonymous type like T'Class is. There is no 
danger of having its copies because it is a by-structure thing and all 
copies are same.

> The problem with all forms of MI is diamond inheritance, which get much
> worse in Ada because of our strict privacy rules.

There is no any problems with diamond inheritance and visibility.

> I don't think that could
> ever be solved for Ada (perhaps a new language could solve it, but not Ada).

It can easily be solved once understood that inheritance may produce 
conflicts to be resolved by the programmer, not by the language, which 
is indeed impossible. Use-clauses, nesting, generic formals have 
*exactly* same issues there is absolutely nothing special in inheritance.

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

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-28  7:31           ` Dmitry A. Kazakov
@ 2016-09-28 20:17             ` Randy Brukardt
  2016-09-29  8:06               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: Randy Brukardt @ 2016-09-28 20:17 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:nsfrn6$ab7$1@gioia.aioe.org...
> On 28/09/2016 02:05, Randy Brukardt wrote:
...
>> The problem with all forms of MI is diamond inheritance, which get much
>> worse in Ada because of our strict privacy rules.
>
> There is no any problems with diamond inheritance and visibility.

Certainly are. If one has a hidden interface with hidden overridden 
operations, and a later extension tries to add the same interface with 
different overridden operations, one of two things has to happen, both bad:
(1) The interfaces are the same, so the hidden operations completely 
disappear. (The new extension cannot know that they ever existed, so they 
cannot be called from it.) Since the parent operation presumably depends on 
those operations, trouble is certain to ensue. (When the operations aren't 
overridden, then they magically become visible, which is also a problem.)
(2) The interfaces are different, so one has problems in dispatching calls 
of figuring out which one to call. That way leads to madness.

The problem here is that only (2) respects privacy at all; (1) eliminates it 
no matter how the conflict is resolved (the fact that the parent has a 
hidden interface changes the semantics of the extension even though the 
extension is not supposed to know or care about the interface).

>> I don't think that could
>> ever be solved for Ada (perhaps a new language could solve it, but not 
>> Ada).
>
> It can easily be solved once understood that inheritance may produce 
> conflicts to be resolved by the programmer, not by the language, which is 
> indeed impossible.

The programmer can't resolve the conflicts unless privacy is ignored, 
because the programmer has no knowledge that the hidden interface is used. 
So why shouldn't they be able to use the same interface?? Any answer breaks 
privacy badly.

Remember, the case in question is an extension from a private view, for 
which no knowledge about the implementation should escape.

> Use-clauses, nesting, generic formals have *exactly* same issues there is 
> absolutely nothing special in inheritance.

And all of those are also evil. :-) They introduce significant maintenance 
problems all of the time. And they don't have the problem of requiring 
visibility on private things to resolve the conflicts. (For all of the 
above, private things stay private.)

                            Randy.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-28 20:17             ` Randy Brukardt
@ 2016-09-29  8:06               ` Dmitry A. Kazakov
  2016-09-29 18:44                 ` Randy Brukardt
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-29  8:06 UTC (permalink / raw)


On 28/09/2016 22:17, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:nsfrn6$ab7$1@gioia.aioe.org...
>> On 28/09/2016 02:05, Randy Brukardt wrote:
> ...
>>> The problem with all forms of MI is diamond inheritance, which get much
>>> worse in Ada because of our strict privacy rules.
>>
>> There is no any problems with diamond inheritance and visibility.
>
> Certainly are. If one has a hidden interface with hidden overridden
> operations, and a later extension tries to add the same interface with
> different overridden operations, one of two things has to happen, both bad:
> (1) The interfaces are the same, so the hidden operations completely
> disappear. (The new extension cannot know that they ever existed, so they
> cannot be called from it.) Since the parent operation presumably depends on
> those operations, trouble is certain to ensue. (When the operations aren't
> overridden, then they magically become visible, which is also a problem.)
> (2) The interfaces are different, so one has problems in dispatching calls
> of figuring out which one to call. That way leads to madness.

It is clearly #2. What you cannot see does not exist, the old good 
solipsism. Ada sometimes violates this principle, always for worse.

There is no problem with dispatching calls so long visibility is respected:

1. Where type's primitive operations are not visible you cannot dispatch 
to them.

2. Where type's primitive operations are visible you cannot derive 
without resolving the conflict between hierarchies.

 From this follows that you cannot use the type in the context where 
hidden operations are visible! The type with an unresolved conflict (and 
its class-type) is invalid. Example:

    package P1 is
       type I is interface;
       procedure Foo (X : I) is abstract;

       type T1 is new T with ...;
    private
       type T1 is new T and I with ...; -- Private interface
    end P1;

    package P2 is -- We don't see private I
       type T2 is new T1 and I with null record; -- This is OK!
    end P2;

    package P1.Child_1 is -- We see private I
       type T2 is new T1 and I with null record; -- This is NOT OK
    end P1.Child_1;

    procedure P1.Child_2 is
       X : P2.T2; -- This is NOT OK, we see two instances of I in X

But of course it is also possible to allow X as we do with other cases 
when operations get hidden by each other. In that scenario X.Foo will be 
rejected as ambiguous. Yes, when X were passed as a class-wide of 
another root type dispatching to Foo would take the hierarchy of I that 
this root type uses. It is all consistent.

       Y : T1'Class := ...; -- This OK, we see only private I
    begin
       Y.Foo; -- This dispatches on I inherited in P1, WYSIWYG

    procedure Public is
       X : P2.T2; -- This is OK, we respect privateness
    begin
       X.Foo; -- This dispatches on I inherited in P2

The important point is that inheritance can be additive as in the case 
when I is inherited once privately and once publicly. If succeeds then 
the outcome is *two* hierarchies.

You cannot forbid that altogether, which is why you see it as a problem. 
It is not a problem, just let it be. We would also allow P1.Child_1 when 
the programmer states that I is inherited additively.

The use case for additive inheritance is multiple linked-lists:

    type Node is limited interface;
    function Next (X : Node) return Node'Class;
    ...

    type IO_Queue is
       new Node and Node with ...;

Each item participates in two lists, the list of requests and the list 
of requests from the same task (i.e. to abort them when the task completes).

> The problem here is that only (2) respects privacy at all;

Right. The user must resolve conflict when it appears. E.g.

    type IO_Queue is
       new Node as Request_List and Node as Task_List with ...;
    function Next_IO_Request ... renames Request_List.Next;
    function Next_Task_Request ... renames Task_List.Next;

I leave syntax to imagination.

>> Use-clauses, nesting, generic formals have *exactly* same issues there is
>> absolutely nothing special in inheritance.
>
> And all of those are also evil. :-)

Yes, the world is a sad place. (:-))

> They introduce significant maintenance
> problems all of the time. And they don't have the problem of requiring
> visibility on private things to resolve the conflicts. (For all of the
> above, private things stay private.)

True, and we could do same for inheritance.

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

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-29  8:06               ` Dmitry A. Kazakov
@ 2016-09-29 18:44                 ` Randy Brukardt
  2016-09-29 19:55                   ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: Randy Brukardt @ 2016-09-29 18:44 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:nsii37$fl3$1@gioia.aioe.org...
> On 28/09/2016 22:17, Randy Brukardt wrote:
>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>> news:nsfrn6$ab7$1@gioia.aioe.org...
>>> On 28/09/2016 02:05, Randy Brukardt wrote:
>> ...
>>>> The problem with all forms of MI is diamond inheritance, which get much
>>>> worse in Ada because of our strict privacy rules.
>>>
>>> There is no any problems with diamond inheritance and visibility.
>>
>> Certainly are. If one has a hidden interface with hidden overridden
>> operations, and a later extension tries to add the same interface with
>> different overridden operations, one of two things has to happen, both 
>> bad:
>> (1) The interfaces are the same, so the hidden operations completely
>> disappear. (The new extension cannot know that they ever existed, so they
>> cannot be called from it.) Since the parent operation presumably depends 
>> on
>> those operations, trouble is certain to ensue. (When the operations 
>> aren't
>> overridden, then they magically become visible, which is also a problem.)
>> (2) The interfaces are different, so one has problems in dispatching 
>> calls
>> of figuring out which one to call. That way leads to madness.
>
> It is clearly #2. What you cannot see does not exist, the old good 
> solipsism. Ada sometimes violates this principle, always for worse.
>
> There is no problem with dispatching calls so long visibility is 
> respected:
>
> 1. Where type's primitive operations are not visible you cannot dispatch 
> to them.

??? You can always convert an object of the type to the class-wide interface 
(which is legal in this scenario, even if not in the private part), and make 
a dispatching call. (That's the case that's a problem.). Which overriding 
operation do you get? If it depends on visibility, you have madness (what 
happens depends on where it is written - yuck). If it doesn't depend on 
visibility, the implementation of the original private type is going to end 
up with the new operations, even though those were not overridden (which is 
likely to break something - essentially you'd be getting the wrong copy of 
the interface).

That of course could occur implicitly inside of a class-wide interface 
operation called by the private implementation (which is the reason for 
using interfaces in the first place), so it's a real issue.

> 2. Where type's primitive operations are visible you cannot derive without 
> resolving the conflict between hierarchies.

Sure. But that doesn't fix anything.

> From this follows that you cannot use the type in the context where hidden 
> operations are visible!

No problem, but the problem is with the type with two copies of the 
interface. How does the conversion to I'Class work (that conversion is 
implicit in calls to class-wide subprograms)? How does it chose which 
interface to use? The correct answer depends on what the programmer 
intended, and that something a compiler (or programming language) can't 
know.

...
> Yes, when X were passed as a class-wide of another root type dispatching 
> to Foo would take the hierarchy of I that this root type uses. It is all 
> consistent.
>
>       Y : T1'Class := ...; -- This OK, we see only private I
>    begin
>       Y.Foo; -- This dispatches on I inherited in P1, WYSIWYG
>
>    procedure Public is
>       X : P2.T2; -- This is OK, we respect privateness
>    begin
>       X.Foo; -- This dispatches on I inherited in P2

Actually, this is madness. Nothing "consistent" about it. Why? Because if 
you move the call to some outside routine (say some other dispatching 
operation), the meaning changes. Which means that one can't use 
abstractions. Very, very bad.

> The important point is that inheritance can be additive as in the case 
> when I is inherited once privately and once publicly. If succeeds then the 
> outcome is *two* hierarchies.
>
> You cannot forbid that altogether, which is why you see it as a problem.

Well, actually we do: we forbid the private inheritance completely, because 
there is no consistent and sensible set of rules for what it would mean.

> It is not a problem, just let it be.

The implementation of two copies of the same interface would break any of 
the existing algorithms for implementing interfaces. (I have absolutely no 
idea how that could be accomplished; it would depend on how the dispatching 
rules were eventually worked out.)

...
>> They introduce significant maintenance
>> problems all of the time. And they don't have the problem of requiring
>> visibility on private things to resolve the conflicts. (For all of the
>> above, private things stay private.)
>
> True, and we could do same for inheritance.

Doesn't help, as noted above.

               Randy.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-29 18:44                 ` Randy Brukardt
@ 2016-09-29 19:55                   ` Dmitry A. Kazakov
  2016-10-01  5:47                     ` Randy Brukardt
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-09-29 19:55 UTC (permalink / raw)


On 2016-09-29 20:44, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:nsii37$fl3$1@gioia.aioe.org...
>> On 28/09/2016 22:17, Randy Brukardt wrote:
>>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>>> news:nsfrn6$ab7$1@gioia.aioe.org...
>>>> On 28/09/2016 02:05, Randy Brukardt wrote:
>>> ...
>>>>> The problem with all forms of MI is diamond inheritance, which get much
>>>>> worse in Ada because of our strict privacy rules.
>>>>
>>>> There is no any problems with diamond inheritance and visibility.
>>>
>>> Certainly are. If one has a hidden interface with hidden overridden
>>> operations, and a later extension tries to add the same interface with
>>> different overridden operations, one of two things has to happen, both
>>> bad:
>>> (1) The interfaces are the same, so the hidden operations completely
>>> disappear. (The new extension cannot know that they ever existed, so they
>>> cannot be called from it.) Since the parent operation presumably depends
>>> on
>>> those operations, trouble is certain to ensue. (When the operations
>>> aren't
>>> overridden, then they magically become visible, which is also a problem.)
>>> (2) The interfaces are different, so one has problems in dispatching
>>> calls
>>> of figuring out which one to call. That way leads to madness.
>>
>> It is clearly #2. What you cannot see does not exist, the old good
>> solipsism. Ada sometimes violates this principle, always for worse.
>>
>> There is no problem with dispatching calls so long visibility is
>> respected:
>>
>> 1. Where type's primitive operations are not visible you cannot dispatch
>> to them.
>
> ??? You can always convert an object of the type to the class-wide interface
> (which is legal in this scenario, even if not in the private part), and make
> a dispatching call. (That's the case that's a problem.).

No, you cannot, you don't see if the type inherits the interface, so you 
cannot convert to its class.

> Which overriding operation do you get?

None, that is illegal, the type does not implement the interface 
inherited privately. There is no overriding, there is no class in any 
public scope.

> If it depends on visibility, you have madness (what
> happens depends on where it is written - yuck).

Nope, that is what visibility all about. You don't see inheritance, you 
don't have it. Period.

>> 2. Where type's primitive operations are visible you cannot derive without
>> resolving the conflict between hierarchies.
>
> Sure. But that doesn't fix anything.
>
>> From this follows that you cannot use the type in the context where hidden
>> operations are visible!
>
> No problem, but the problem is with the type with two copies of the
> interface. How does the conversion to I'Class work (that conversion is
> implicit in calls to class-wide subprograms)?

It will work just fine. In any context only one inheritance is visible 
or else conversion is ambiguous and thus illegal (unless resolved).

> How does it chose which interface to use?

The one visible in this context. There is no unresolved conflicts, in 
any given context it is unambiguous.

> ...
>> Yes, when X were passed as a class-wide of another root type dispatching
>> to Foo would take the hierarchy of I that this root type uses. It is all
>> consistent.
>>
>>       Y : T1'Class := ...; -- This OK, we see only private I
>>    begin
>>       Y.Foo; -- This dispatches on I inherited in P1, WYSIWYG
>>
>>    procedure Public is
>>       X : P2.T2; -- This is OK, we respect privateness
>>    begin
>>       X.Foo; -- This dispatches on I inherited in P2
>
> Actually, this is madness. Nothing "consistent" about it. Why? Because if
> you move the call to some outside routine (say some other dispatching
> operation), the meaning changes. Which means that one can't use
> abstractions. Very, very bad.

Nothing mad here. You change the type of X that changes the operation. 
Why X / Y must mean same for Integer X, Y and Float X, Y? How Foo is 
different from "/"?

>> The important point is that inheritance can be additive as in the case
>> when I is inherited once privately and once publicly. If succeeds then the
>> outcome is *two* hierarchies.
>>
>> You cannot forbid that altogether, which is why you see it as a problem.
>
> Well, actually we do: we forbid the private inheritance completely, because
> there is no consistent and sensible set of rules for what it would mean.

By violating privacy, by introducing ugly artifacts, by piling up 
nonsense rules...

>> It is not a problem, just let it be.
>
> The implementation of two copies of the same interface would break any of
> the existing algorithms for implementing interfaces. (I have absolutely no
> idea how that could be accomplished; it would depend on how the dispatching
> rules were eventually worked out.)

It breaks nothing. You get two hierarchies independent on each other. 
You already can do just this with ease:

    generic
    package P is
       type I is interface;
       procedure Foo (X : I) is abstract;
    end P;

    package P1 is new P;
    package P2 is new P;

    type T is new P1.I and P2.I with null record;
    overriding procedure Foo (X : T);

How is it different from:

    type T is new I and I with null record;

Or this:

    generic
       type S is (<>);
    package P is
       type I is interface;
       procedure Foo (X : I; Y : S) is abstract;
    end P;

    type T is new P1.I and P2.I with null record;
    overriding procedure Foo (X : T; Y : Integer);
    overriding procedure Foo (X : T; Y : Boolean);

You cannot prevent things like this once you introduced MI. There is 
only one way to do MI right and it is not the one Ada presently goes.

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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-09-29 19:55                   ` Dmitry A. Kazakov
@ 2016-10-01  5:47                     ` Randy Brukardt
  2016-10-01  8:35                       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 14+ messages in thread
From: Randy Brukardt @ 2016-10-01  5:47 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:nsjrjo$lg8$1@gioia.aioe.org...
> On 2016-09-29 20:44, Randy Brukardt wrote:
>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>> news:nsii37$fl3$1@gioia.aioe.org...
>>> On 28/09/2016 22:17, Randy Brukardt wrote:
>>>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>>>> news:nsfrn6$ab7$1@gioia.aioe.org...
>>>>> On 28/09/2016 02:05, Randy Brukardt wrote:
>>>> ...
>>>>>> The problem with all forms of MI is diamond inheritance, which get 
>>>>>> much
>>>>>> worse in Ada because of our strict privacy rules.
>>>>>
>>>>> There is no any problems with diamond inheritance and visibility.
>>>>
>>>> Certainly are. If one has a hidden interface with hidden overridden
>>>> operations, and a later extension tries to add the same interface with
>>>> different overridden operations, one of two things has to happen, both
>>>> bad:
>>>> (1) The interfaces are the same, so the hidden operations completely
>>>> disappear. (The new extension cannot know that they ever existed, so 
>>>> they
>>>> cannot be called from it.) Since the parent operation presumably 
>>>> depends
>>>> on
>>>> those operations, trouble is certain to ensue. (When the operations
>>>> aren't
>>>> overridden, then they magically become visible, which is also a 
>>>> problem.)
>>>> (2) The interfaces are different, so one has problems in dispatching
>>>> calls
>>>> of figuring out which one to call. That way leads to madness.
>>>
>>> It is clearly #2. What you cannot see does not exist, the old good
>>> solipsism. Ada sometimes violates this principle, always for worse.
>>>
>>> There is no problem with dispatching calls so long visibility is
>>> respected:
>>>
>>> 1. Where type's primitive operations are not visible you cannot dispatch
>>> to them.
>>
>> ??? You can always convert an object of the type to the class-wide 
>> interface
>> (which is legal in this scenario, even if not in the private part), and 
>> make
>> a dispatching call. (That's the case that's a problem.).
>
> No, you cannot, you don't see if the type inherits the interface, so you 
> cannot convert to its class.

We're talking about the type that both publically and privately implements 
the same interface. (The private one inherited from the parent, the public 
one directly added.) And we're talking about a conversion that occurs where 
the private interface is visible (so *both* copies of the interface are 
visible). As I said:

>> Which overriding operation do you get?
>
> None, that is illegal, the type does not implement the interface inherited 
> privately. There is no overriding, there is no class in any public scope.

You're not thinking broadly enough about all of the possible combinations of 
public and private inheritance. It just doesn't work. Yes, the "normal" 
cases work, but we have to allow all possibilities (or we're breaking 
privacy).

In particular, when you are in the private scope, you can see both 
interfaces. Whichever one you chose will be wrong about half of the time. 
Such a capability isn't going to help users much, as there will be innumable 
problems caused by it.

...
>> How does it chose which interface to use?
>
> The one visible in this context. There is no unresolved conflicts, in any 
> given context it is unambiguous.

??? Both interfaces are visible in the private context. How could they not 
be both visible  (they're distinct)?


>> ...
>>> Yes, when X were passed as a class-wide of another root type dispatching
>>> to Foo would take the hierarchy of I that this root type uses. It is all
>>> consistent.
>>>
>>>       Y : T1'Class := ...; -- This OK, we see only private I
>>>    begin
>>>       Y.Foo; -- This dispatches on I inherited in P1, WYSIWYG
>>>
>>>    procedure Public is
>>>       X : P2.T2; -- This is OK, we respect privateness
>>>    begin
>>>       X.Foo; -- This dispatches on I inherited in P2
>>
>> Actually, this is madness. Nothing "consistent" about it. Why? Because if
>> you move the call to some outside routine (say some other dispatching
>> operation), the meaning changes. Which means that one can't use
>> abstractions. Very, very bad.
>
> Nothing mad here. You change the type of X that changes the operation. Why 
> X / Y must mean same for Integer X, Y and Float X, Y? How Foo is different 
> from "/"?
>
>>> The important point is that inheritance can be additive as in the case
>>> when I is inherited once privately and once publicly. If succeeds then 
>>> the
>>> outcome is *two* hierarchies.
>>>
>>> You cannot forbid that altogether, which is why you see it as a problem.
>>
>> Well, actually we do: we forbid the private inheritance completely, 
>> because
>> there is no consistent and sensible set of rules for what it would mean.
>
> By violating privacy, by introducing ugly artifacts, by piling up nonsense 
> rules...

There's no violation of privacy. It's illegal to add an interface in a 
private part, but you can of course add the interface visibly. It was the 
best idea that we could come up with, and it had the advantage that if we 
ever could figure out some meaningful semantics that it would be compatible 
to allow private interfaces in the future. The worst possible thing is to 
allow something but get it wrong, because then one have to break all the 
existing usage to fix it (or worse, not fix it).

>>> It is not a problem, just let it be.
>>
>> The implementation of two copies of the same interface would break any of
>> the existing algorithms for implementing interfaces. (I have absolutely 
>> no
>> idea how that could be accomplished; it would depend on how the 
>> dispatching
>> rules were eventually worked out.)
>
> It breaks nothing. You get two hierarchies independent on each other.

Sorry, but you don't have any idea how interface dispatching works. It's 
typically done with some sort of lookup table (since the typical solution of 
ensuring that all of the slots are the same in all of descendants does not 
work when multiple inheritance is allowed). That works because there is only 
one copy of each interface, so there is no need to worry about where they 
come from - one just needs a unique Id where they are declared. If there can 
be multiple copies, that doesn't work. And since these are runtime 
operations,  there doesn't seem to be any way to choose between them --  
runtime knows nothing of visibility.



> You already can do just this with ease:
>
>    generic
>    package P is
>       type I is interface;
>       procedure Foo (X : I) is abstract;
>    end P;
>
>    package P1 is new P;
>    package P2 is new P;
>
>    type T is new P1.I and P2.I with null record;
>    overriding procedure Foo (X : T);

P1.I and P2.I are two different types. In the privacy case, you are getting 
two copies of the same type. Not remotely the same thing. (You can tell the 
difference by trying to call a global class-wide operation that has a 
parameter of the interface. For this example, that won't work for both 
interfaces -- one or the other isn't going to be allowed. For the private 
example, one could call that routine with either interface (because they 
have the same name) -- that's the crux of the problem.

> How is it different from:
>
>    type T is new I and I with null record;

There is only one copy of the interface in this type. It doesn't matter how 
many times you mention it in the declaration or inherit it. But if that was 
applied to private types, it would be what I called case (1), and it would 
badly break privacy.

                             Randy.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-10-01  5:47                     ` Randy Brukardt
@ 2016-10-01  8:35                       ` Dmitry A. Kazakov
  2016-10-05 20:42                         ` Randy Brukardt
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry A. Kazakov @ 2016-10-01  8:35 UTC (permalink / raw)


On 2016-10-01 07:47, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:nsjrjo$lg8$1@gioia.aioe.org...
>> On 2016-09-29 20:44, Randy Brukardt wrote:
>>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>>> news:nsii37$fl3$1@gioia.aioe.org...
>>>> On 28/09/2016 22:17, Randy Brukardt wrote:
>>>>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>>>>> news:nsfrn6$ab7$1@gioia.aioe.org...
>>>>>> On 28/09/2016 02:05, Randy Brukardt wrote:
>>>>> ...
>>>>>>> The problem with all forms of MI is diamond inheritance, which get
>>>>>>> much
>>>>>>> worse in Ada because of our strict privacy rules.
>>>>>>
>>>>>> There is no any problems with diamond inheritance and visibility.
>>>>>
>>>>> Certainly are. If one has a hidden interface with hidden overridden
>>>>> operations, and a later extension tries to add the same interface with
>>>>> different overridden operations, one of two things has to happen, both
>>>>> bad:
>>>>> (1) The interfaces are the same, so the hidden operations completely
>>>>> disappear. (The new extension cannot know that they ever existed, so
>>>>> they
>>>>> cannot be called from it.) Since the parent operation presumably
>>>>> depends
>>>>> on
>>>>> those operations, trouble is certain to ensue. (When the operations
>>>>> aren't
>>>>> overridden, then they magically become visible, which is also a
>>>>> problem.)
>>>>> (2) The interfaces are different, so one has problems in dispatching
>>>>> calls
>>>>> of figuring out which one to call. That way leads to madness.
>>>>
>>>> It is clearly #2. What you cannot see does not exist, the old good
>>>> solipsism. Ada sometimes violates this principle, always for worse.
>>>>
>>>> There is no problem with dispatching calls so long visibility is
>>>> respected:
>>>>
>>>> 1. Where type's primitive operations are not visible you cannot dispatch
>>>> to them.
>>>
>>> ??? You can always convert an object of the type to the class-wide
>>> interface
>>> (which is legal in this scenario, even if not in the private part), and
>>> make a dispatching call. (That's the case that's a problem.).
>>
>> No, you cannot, you don't see if the type inherits the interface, so you
>> cannot convert to its class.
>
> We're talking about the type that both publically and privately implements
> the same interface.

In the example provided it is P1.Child_1.T2.

> (The private one inherited from the parent, the public
> one directly added.) And we're talking about a conversion that occurs where
> the private interface is visible (so *both* copies of the interface are
> visible).

What converted to the type? But it is irrelevant anyway. The interface 
instances were already visible in the private part of the package where 
the offending type was declared. The conflict was *already* resolved 
there, or the type declaration rejected.

>>> Which overriding operation do you get?
>>
>> None, that is illegal, the type does not implement the interface inherited
>> privately. There is no overriding, there is no class in any public scope.
>
> You're not thinking broadly enough about all of the possible combinations of
> public and private inheritance. It just doesn't work. Yes, the "normal"
> cases work, but we have to allow all possibilities (or we're breaking
> privacy).  In particular, when you are in the private scope, you can see both
> interfaces.

I don't understand why you think so. Can you provide an example with the 
type declarations I gave?

>>> How does it chose which interface to use?
>>
>> The one visible in this context. There is no unresolved conflicts, in any
>> given context it is unambiguous.
>
> ??? Both interfaces are visible in the private context. How could they not
> be both visible  (they're distinct)?

Because declaration of P1.Child_1.T2 is illegal. To make it legal at 
least one of the interfaces must be renamed.

>> By violating privacy, by introducing ugly artifacts, by piling up nonsense
>> rules...
>
> There's no violation of privacy. It's illegal to add an interface in a
> private part,

That constitutes violation of privacy. Usually it is more that you can 
do privately than publicly (:-))

>>>> It is not a problem, just let it be.
>>>
>>> The implementation of two copies of the same interface would break any of
>>> the existing algorithms for implementing interfaces. (I have absolutely
>>> no
>>> idea how that could be accomplished; it would depend on how the
>>> dispatching
>>> rules were eventually worked out.)
>>
>> It breaks nothing. You get two hierarchies independent on each other.
>
> Sorry, but you don't have any idea how interface dispatching works. It's
> typically done with some sort of lookup table (since the typical solution of
> ensuring that all of the slots are the same in all of descendants does not
> work when multiple inheritance is allowed).

The slots in the dispatching table are different because interfaces are 
different.

> That works because there is only
> one copy of each interface, so there is no need to worry about where they
> come from - one just needs a unique Id where they are declared. If there can
> be multiple copies, that doesn't work.

The copies get different IDs in the combined dispatching table, that is 
the purpose of renaming/conflict resolution.

> And since these are runtime
> operations

It is no different from how dispatching works under MI. You must slide 
the table when moving toward the roots. In most general case you have

    Tag x Operation -> Body

So it is T x I(1) and T x I(2), both different. You might have 
difficulties with interpretation re-dispatch if a body is inherited 
as-as, but that is only for full MI, we don't have still. Clearly 
I'Class in an inherited body must mean "the class of my I".

>> You already can do just this with ease:
>>
>>    generic
>>    package P is
>>       type I is interface;
>>       procedure Foo (X : I) is abstract;
>>    end P;
>>
>>    package P1 is new P;
>>    package P2 is new P;
>>
>>    type T is new P1.I and P2.I with null record;
>>    overriding procedure Foo (X : T);
>
> P1.I and P2.I are two different types.

BINGO!

> In the privacy case, you are getting
> two copies of the same type.

No, you get two different types (interfaces) if the conflict resolved.

> Not remotely the same thing. (You can tell the
> difference by trying to call a global class-wide operation that has a
> parameter of the interface. For this example, that won't work for both
> interfaces -- one or the other isn't going to be allowed. For the private
> example, one could call that routine with either interface (because they
> have the same name) -- that's the crux of the problem.

Exactly, it is ambiguous just same as if in the body of P there were

    Bar (X : I'Class);

overloaded from instances of P1 and P2.

>> How is it different from:
>>
>>    type T is new I and I with null record;
>
> There is only one copy of the interface in this type.

No, there are two. And calling to Bar is ambiguous. The conflict must be 
resolved by naming a distinct I's in T. Once they named T gets 
independent sets of slots for each of the primitive operations of I. To 
be able to call Bar where you see both I's you will have to explicitly 
convert T to either of two I'Class. That will slide to the corresponding 
section of the dispatching table corresponding to the chosen I.

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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Class-wide types algebra
  2016-10-01  8:35                       ` Dmitry A. Kazakov
@ 2016-10-05 20:42                         ` Randy Brukardt
  0 siblings, 0 replies; 14+ messages in thread
From: Randy Brukardt @ 2016-10-05 20:42 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:nsnsho$6om$1@gioia.aioe.org...
> On 2016-10-01 07:47, Randy Brukardt wrote:
...
>> We're talking about the type that both publically and privately 
>> implements
>> the same interface.
>
> In the example provided it is P1.Child_1.T2.
>
>> (The private one inherited from the parent, the public
>> one directly added.) And we're talking about a conversion that occurs 
>> where
>> the private interface is visible (so *both* copies of the interface are
>> visible).
>
> What converted to the type? But it is irrelevant anyway. The interface 
> instances were already visible in the private part of the package where 
> the offending type was declared. The conflict was *already* resolved 
> there, or the type declaration rejected.

I need to write out the entire example to get a sensible answer from you. I 
unfortunately don't have time, as I have to get the agenda ready for the ARG 
meeting that starts on Saturday (and of course I'll be traveling all day 
Friday). So I'm going to have to drop this discussion. (So I didn't read 
your reply, sorry.)

The problem eventually becomes a run-time problem, since type conversions 
are primarily runtime artifacts. That's very ugly.

And you can't reject the declaration that has two distinct copies of the 
same interface, because that would clearly violate privacy. So you get into 
a situation where you have to chose between two copies of an interface, with 
identical names, at runtime, presumably based on visibility. That's not 
going to work.

                                           Randy.

P.S. Dmitry, Don't bother answering, I'm not going to read it or answer. 
Just can't afford the time. Sorry.


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2016-10-05 20:42 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-12 20:26 Class-wide types algebra Dmitry A. Kazakov
2016-09-12 21:12 ` rieachus
2016-09-12 21:39   ` Dmitry A. Kazakov
2016-09-13 11:46     ` rieachus
2016-09-13 12:26       ` Dmitry A. Kazakov
2016-09-28  0:05         ` Randy Brukardt
2016-09-28  7:31           ` Dmitry A. Kazakov
2016-09-28 20:17             ` Randy Brukardt
2016-09-29  8:06               ` Dmitry A. Kazakov
2016-09-29 18:44                 ` Randy Brukardt
2016-09-29 19:55                   ` Dmitry A. Kazakov
2016-10-01  5:47                     ` Randy Brukardt
2016-10-01  8:35                       ` Dmitry A. Kazakov
2016-10-05 20:42                         ` Randy Brukardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox