* 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