Version 1.1 of acs/ac-00190.txt

Unformatted version of acs/ac-00190.txt version 1.1
Other versions for file acs/ac-00190.txt

!standard 13.13.1(3/2)          10-02-13 AC95-00190/01
!class Amendment 10-02-13
!status received no action 10-02-13
!status received 10-01-15
!subject Change type of Root_Stream_Type to interface
!summary
!appendix

From: Vadim Godunko
Date: Friday, January 15, 2010  1:04 PM

It would be nice to declare Ada.Streams.Root_Stream_Type as

   type Root_Stream_Type is limited interface;

to allows to support stream operations by objects with others root type.

****************************************************************

From: Dmitry A. Kazakov
Date: Friday, January 15, 2010  2:55 PM

Also

type Root_Storage_Pool is limited interface; type Controlled is interface; type
Limited_Controlled is limited interface;

****************************************************************

From: Pascal Leroy
Date: Sunday, January 17, 2010  4:51 AM

This was considered during the Ada 2005 design, but it's not possible because it
would introduce horrendous incompatibilities due to the "no hidden interfaces"
rule (RM95 7.3(7.3/2)).

****************************************************************

From: Bob Duff
Date: Sunday, January 17, 2010  9:00 AM

Right.

It is quite common to inherit privately from [Limited_]Controlled.
Root_Storage_Pool and Root_Stream_Type, not so much, but it's still an
incompatibility.

Also, for [Limited_]Controlled, there would be an implementation burden, because
some implementations declare these types to have private components (e.g. links
to chain controlled objects together).  GNAT currently does that, although the
plan is to eliminate that someday.

And Root_Storage_Pool is derived from Limited_Controlled.

Root_Stream_Type is likely just a "tagged limited null record".

****************************************************************

From: Vadim Godunko
Date: Sunday, January 17, 2010  11:11 AM

> This was considered during the Ada 2005 design, but it's not possible
> because it would introduce horrendous incompatibilities due to the "no
> hidden interfaces" rule (RM95 7.3(7.3/2)).

Can you please provide such an example in context of Root_Stream_Type?

****************************************************************

From: Vadim Godunko
Date: Sunday, January 17, 2010  11:23 AM

So, it is good candidate to be declared as interface. In real life it definitely
simplify things, for example:

package IO_Devices is

   type IO_Device is
     abstract limited new Root_Stream_Type with private;

private

   type IO_Device is
     abstract new Limited_Controlled
       and Root_Stream_Type with SOME record;

end IO_Devices;

package Files is

   type File is new IO_Device with private;

end Files;

package Sockets is

   type Socket is new IO_Device with private;

end Sockets;

Of course, the same effect can be achieved by adding operation to IO_Device
which returns access to Root_Stream_Type, but from the current state of OOP art
it looks not very good (and don't forgot about potential dangling pointer).

****************************************************************

From: Dmitry A. Kazakov
Date: Sunday, January 17, 2010  1:26 PM

...
> package IO_Devices is
>
>    type IO_Device is
>      abstract limited new Root_Stream_Type with private;

Device is almost always will be a descendant of Limited_Controlled. At least it
is so in our design.

> Of course, the same effect can be achieved by adding operation to
> IO_Device which returns access to Root_Stream_Type,

Yes, this is how we resolve it, because controlledness is more important than
stream interface. Similarly it goes for the task each device normally
encapsulates (to which I/O requests get queued).

Unfortunately the language makes it is impossible to have a device, which is a
task, a stream and controlled at the same time.

> but from the current
> state of OOP art it looks not very good (and don't forgot about
> potential dangling pointer).

In our case the result is an anonymous not-null-access type, which reduces
potential damage to some extent.

****************************************************************

From: Bob Duff
Date: Sunday, January 17, 2010  11:30 AM

The following is legal Ada.  It would be illegal if Root_Stream_Type were an
interface.

I do not claim that this sort of thing is likely to be common in real code;
usually if you want to derive from Root_Stream_Type, you want to do so visibly.
But who knows?  Maybe the stream-ness of type T only needs to be visible in
children of P.

Is the benefit of making Root_Stream_Type an interface worth the possible
(unknown) cost of incompatibility?  I don't know.

   with Ada.Streams; use Ada.Streams;
   package P is
      type T (<>) is limited private;
      function F return T;
   private
      type T is new Root_Stream_Type with null record;

      procedure Read
        (Stream : in out T;
         Item   : out Stream_Element_Array;
         Last   : out Stream_Element_Offset);

      procedure Write
        (Stream : in out T;
         Item   : Stream_Element_Array);

   end P;

   package body P is

      function F return T is
      begin
         return (Root_Stream_Type with null record);
      end F;

      procedure Read
        (Stream : in out T;
         Item   : out Stream_Element_Array;
         Last   : out Stream_Element_Offset) is
      begin
         null;
      end Read;

      procedure Write
        (Stream : in out T;
         Item   : Stream_Element_Array) is
      begin
         null;
      end Write;

   end P;

****************************************************************

From: Bob Duff
Date: Sunday, January 17, 2010  4:00 PM

> So, it is good candidate to be declared as interface. In real life it
> definitely simplify things,...

I agree it would be better as an interface.  I guess that's true in general --
any type that is "null record" and has only 'abstract' and 'null' procedures,
would be more powerful as an interface.

If Ada 95 had interfaces, I suppose we would have done it that way.

The only question is, "Is it worth the incompatibility?"

...
> Of course, the same effect can be achieved by adding operation to
> IO_Device which returns access to Root_Stream_Type, but from the
> current state of OOP art it looks not very good (and don't forgot
> about potential dangling pointer).

Or, in this example, you could add a controlled component to File and Socket.
You might not want the expense of controlled types for ALL IO_Devices -- for
example the null device (like /dev/null or "the bit bucket") doesn't need any
finalization.

But I agree there's some benefit to making Root_Stream_Type an interface.
And some unknown but small-ish cost.

For [Limited_]Controlled, no way!

****************************************************************

From: Dmitry A. Kazakov
Date: Sunday, January 17, 2010  3:14 PM

> Also, for [Limited_]Controlled, there would be an implementation
> burden, because some implementations declare these types to have
> private components (e.g. links to chain controlled objects together).

You can add them at the point where a type starts to implement the "interface".

It is clear that neither of them are interfaces, they are abstract types with
certain implementation wired up by the compiler. The motivation to make them
"interfaces" is to achieve MI, where it is otherwise forbidden.

****************************************************************

From: Cyrille Comar
Date: Sunday, January 17, 2010  2:50 PM

> The following is legal Ada.  It would be illegal if Root_Stream_Type
> were an interface.

rather than transforming Root_Stream_Type into an interface, can't we add a new
predefined interface called "Streams" (since it is not really a Root nor a type)
that would have the expected similar semantics? That is not pretty in term of
language design but it provides the needed feature and does not create
incompatibility...

(same thing for Controlled and Limited_Controlled)

****************************************************************

From: Bob Duff
Date: Monday, January 18, 2010  2:36 PM

> rather than transforming Root_Stream_Type into an interface, can't we
> add a new predefined interface called "Streams" (since it is not
> really a Root nor a type) that would have the expected similar semantics?
> That is not pretty in term of language design but it provides the needed
>   feature and does not create incompatibility...

Yes, I think that would work.  So Root_Stream_Type would be derived from this
new interface.

> (same thing for Controlled and Limited_Controlled)

That would work too, from a compatibility point of view, but I still think
there's an implementation issue.

Certainly it's possible, as Dmitry Kazakov said:

> > Also, for [Limited_]Controlled, there would be an implementation
> > burden, because some implementations declare these types to have
> > private components (e.g. links to chain controlled objects together).
>
> You can add them at the point where a type starts to implement the
> "interface".

But not trivial.  I wouldn't be surprised if this cost many person-months, for
some implementations.

****************************************************************

From: Vadim Godunko
Date: Monday, January 18, 2010  2:41 PM

> rather than transforming Root_Stream_Type into an interface, can't we
> add a new predefined interface called "Streams" (since it is not
> really a Root nor a type) that would have the expected similar semantics?
> That is not pretty in term of language design but it provides the
> needed  feature and does not create incompatibility...

Doesn't help as pointed by Pascal

****************************************************************

From: Bob Duff
Date: Monday, January 18, 2010  4:12 PM

> Doesn't help as pointed by Pascal

He did?  The only message I got from Pascal on this subject was the following:

> This was considered during the Ada 2005 design, but it's not possible
> because it would introduce horrendous incompatibilities due to the "no
> hidden interfaces" rule (RM95 7.3(7.3/2)).

Did I miss something?

Above, Pascal is talking about this:

    type Limited_Controlled is limited interface;

which would be severely incompatible.

Cyrille is talking about something different:

    type Limited_Controlled_Interface is limited interface;
    type Limited_Controlled is new Limited_Controlled_Interface with private;

This would be compatible, except for the minor issue of polluting the namespace
with "Limited_Controlled_Interface", and even that could be eliminated by
putting it in a different package.

****************************************************************

From: Randy Brukardt
Date: Monday, January 18, 2010  5:39 PM

...
> Cyrille is talking about something different:
>
>     type Limited_Controlled_Interface is limited interface;
>     type Limited_Controlled is new
> Limited_Controlled_Interface with private;
>
> This would be compatible, except for the minor issue of polluting the
> namespace with "Limited_Controlled_Interface", and even that could be
> eliminated by putting it in a different package.

I think Vadim is correct; the above has the same incompatibility problem as the
original idea (you'd still be hiding the Limited_Controlled_Interface, and that
is illegal whether or not it is directly declared). After all, the partial view
would still fail the test (it would not be a descendent of
Limited_Controlled_Interface, as required by 7.3(7.3/2)).

The only way to make it OK would be for them to be totally unrelated:

     type Limited_Controlled_Interface is limited interface;
     type Limited_Controlled is abstract tagged limited private;

Which is pretty weird. Moreover, I would still object: implementing hidden
components for this one case of interfaces would be as costly as adding them for
all types. If we are willing to do that, there would have been no reason to not
implement full multiple inheritance, and we would not need interfaces at all.
(After all, the major difference between abstract types and interfaces is the
ability to have components.)

There are still many of us that are unconvinced that multiple inheritance is
really worth the extreme implementation costs (interfaces is one feature that
you don't have to worry about appearing in Janus/Ada anytime soon), so I think
there would be a lot of opposition to extending it even further.

Anyway, I wouldn't be as adverse to making this change for streams or storage
pools, but the incompatibility is definitely scary. Moreover, in the case of
streams, it doesn't help that much. The problem is that you still can only have
one kind of stream, but it makes sense to have multiple kinds (binary, XML,
portable binary, etc.). The *real* problem isn't streams, but rather the fact
that you can't get the composition benefits of streams in code that you write
yourself. I'm not sure that there is a solution (we've tried to look for such
solutions in the past, but it still is annoying that there is compiler magic
here (and also for equality) that you can't use in your own abstractions.

****************************************************************

From: Bob Duff
Date: Monday, January 18, 2010  6:29 PM

> I think Vadim is correct; the above has the same incompatibility
> problem as the original idea (you'd still be hiding the
> Limited_Controlled_Interface, and that is illegal whether or not it is
> directly declared). After all, the partial view would still fail the
> test (it would not be a descendent of Limited_Controlled_Interface, as
> required by 7.3(7.3/2)).

OK, I see.

****************************************************************

Editor's note, February 13, 2010.

In addition to the concerns above, there is also the problem that
implementations supporting multiple Ada versions (Ada 95, Ada 2005, Ada 2012)
via a compiler switch (which is most of them), changing the kind of a
predefined type is likely to be a lot harder than simply changing the values
of entities or simply ignoring some declarations. That clearly would
increase the implementation burden.

****************************************************************


Questions? Ask the ACAA Technical Agent