comp.lang.ada
 help / color / mirror / Atom feed
* Smart Pointers and Tagged Type Hierarchies
@ 2017-07-24 15:41 Felix Krause
  2017-07-24 19:53 ` Dmitry A. Kazakov
                   ` (4 more replies)
  0 siblings, 5 replies; 15+ messages in thread
From: Felix Krause @ 2017-07-24 15:41 UTC (permalink / raw)


With Ada's controlled types, it is possible to implement smart pointers 
which manage heap objects with reference-counting. There is more than 
one tutorial showing how that works.

A problem I encounter is how this can be used with type hierarchies 
i.e. I have a smart pointer managing a tagged type, and I want to be 
able to derive from that tagged type and still be able to use my smart 
pointer with that new type. Let me give an example: Assume I want to 
implement an abstract type Stream that represents a stream of events 
(has nothing to do with Ada.Streams). I will use a slightly modified 
Rosen '95 name scheme here for clarity: Reference is the smart pointer, 
Instance is the actual object. Let this be the base type:

    package Stream is
       type Reference is new Ada.Finalization.Controlled with private;

       type Instance is abstract tagged limited private;
       type Instance_Pointer is access all Instance'Class;

       --  reference-counting implementation here
       overriding procedure Adjust (Object : in out Reference);
       overriding procedure Finalize (Object : in out Reference);

       --  fetches an event from the stream
       procedure Fetch (Object : in out Instance; Ret : out Event) is abstract;

       --  initialize the smart pointer with an object.
       --  the smart pointer takes control of that object and will 
deallocate it
       --  when reference count reaches zero.
       procedure Init (Object : in out Reference'Class; Impl : 
Instance_Pointer);

       function Implementation_Access (Object : Reference'Class) return 
Instance_Pointer;

       --  is called before deleting the instance. override if you have 
cleanup to do.
       procedure Finalize (Object : in out Instance) is null;
    private
       type Reference is new Ada.Finalization.Controlled with record
          Impl : Instance_Pointer;
       end record;

       type Instance is abstract tagged limited record
          Refcount : Natural := 1;
       end record;
    end Stream;

An example non-abstract type derived from this would be stream that 
reads events from a file:

    package File_Stream is
       type Reference is new Stream.Reference with null record;

       procedure Init (Object : in out Reference; Path : String);

       --  fetches the current position within the file
       procedure Current_Position (Object : in out Reference; Line, 
Column : out Positive);
    private
       type Instance is new Instance with record
          File : Ada.Text_IO.File_Access;
          --  possibly other fields, e.g. information needed for 
Current_Position
       end record;

       --  closes the file
       overriding procedure Finalize (Object : in out Instance);
    end File_Stream;

Some observations:

 * Unless all implementations are child classes of Stream, it is 
necessary to make the Instance type public.
 * A derived type, if it wants to provide additional operations (like 
Current_Position), must not only derive from Instance, but also from 
Reference, to be able to provide an type-safe interface to those 
operations.
 * As types derived from Stream possibly need to derive 
Stream.Reference, a consumer of a Stream object needs to take a 
Stream.Reference'Class as input. This type cannot be used for a record 
field, so I need to allocate it in heap memory and store a pointer if I 
want to memorize a Stream.Reference value anywhere.
 * The implementation of Current_Position is cumbersome as I need the 
Implementation_Access function and convert the result to 
File_Stream.Instance, which creates a needless downcast check.

I think this is not an ideal interface for the user and I am searching 
for a better alternative. One thing I thought of is having a generic 
pointer, so that only the Instance is tagged:

    package Stream is
       type Instance is abstract limited tagged private;

       --  fetches an event from the stream
       procedure Fetch (Object : in out Instance; Ret : out Event) is abstract;
    private
       type Instance is abstract tagged limited record
          Refcount : Natural := 1;
       end record;
    end Stream;

    generic
       type Implementation is new Stream.Instance with private;
    package Stream.Smart is
       type Reference is new Ada.Finalization.Controlled with private;

       --  reference-counting implementation
       overriding procedure Adjust (Object : in out Reference);
       overriding procedure Finalize (Object : in out Reference);
    private
       type Implementation_Access is access all Implementation'Class;

       type Reference is new Ada.Finalization.Controlled with record
          Data : access Implementation_Access;
       end record;
    end Stream.Smart;

This looks good at first glance. But now, all consumers of a Stream 
must also be generic and take an instance of the Smart package as 
generic parameter (at least if I want them to take the smart pointer 
and not Instance_Pointer as parameter, which is kind of the point).

Now I am wondering what others think of these approaches. Are there 
alternatives? Which one would be better from a user perspective?

-- 
Regards,
Felix Krause


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
@ 2017-07-24 19:53 ` Dmitry A. Kazakov
  2017-07-27 19:30   ` Felix Krause
  2017-07-24 21:24 ` Chris Moore
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 15+ messages in thread
From: Dmitry A. Kazakov @ 2017-07-24 19:53 UTC (permalink / raw)


On 2017-07-24 17:41, Felix Krause wrote:

> Now I am wondering what others think of these approaches. Are there 
> alternatives? Which one would be better from a user perspective?

I am using generic pointer (Handle), which can be instantiated later 
with any derived type from the base reference counting type. See Simple 
Components:

    http://www.dmitry-kazakov.de/ada/components.htm#Objects_etc

This is your second approach but with object pointer type passed as a 
second generic parameter.

generic
    type Object_Type (<>) is abstract new Entity with private;
    type Object_Type_Ptr is access Object_Type'Class;
package Object.Handle is
    type Handle is new Ada.Finalization.Controlled with private;

Users of smart pointer need not to be generic. I don't know why you 
think it is necessary.

Regarding hierarchy of types and additional operations and the problem 
of exposing the implementation type. You can use delegation:

E.g. if you have your File_Stream_Object and File_Stream_Reference, you 
define a File_Stream_Interface:

    type File_Stream_Interface is interface;
    procedure Foo (Stream : in out File_Stream_Interface) is abstract;

Then both object and reference implement the interface:

    type File_Stream_Object is
       new Instance and File_Stream_Interface ...
    overriding procedure Foo (Stream : in out File_Stream_Reference);

    type File_Stream_Reference is
       new Reference and File_Stream_Interface ...
    overriding procedure Foo (Stream : in out File_Stream_Reference);

 From second Foo you call the first after dereferencing. Now you can 
hide File_Stream_Object and expose only File_Stream_Interface and 
File_Stream_Reference. You can also derive from File_Stream_Object and 
File_Stream_Reference adding new interfaces. The latter has a drawback 
that you will have to convert pointer to a more specific class.

------------------------------------
It is quite tedious because:

1. Delegation cannot be automated in Ada;
2. Pointer are not promoted upon inheritance.

Otherwise this schema works well.

If I were to propose new Ada features, which is of course would be 
pointless, I would solve #1 with providing access to interface 
implementation to generate wrappers:

    type File_Stream_Reference is
       new Reference
       and File_Stream_Interface ...;
private
    type File_Stream_Reference is
       new Reference
       and File_Stream_Interface ...
          with Implicit_Delegation => Data;

#2 could be solved by constraint propagation from Reference type to Data 
access member.

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

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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
  2017-07-24 19:53 ` Dmitry A. Kazakov
@ 2017-07-24 21:24 ` Chris Moore
  2017-07-27 19:38   ` Felix Krause
  2017-07-26 17:53 ` Ivan Levashev
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 15+ messages in thread
From: Chris Moore @ 2017-07-24 21:24 UTC (permalink / raw)


On 24/07/2017 16:41, Felix Krause wrote:
> With Ada's controlled types, it is possible to implement smart pointers 
> which manage heap objects with reference-counting. There is more than 
> one tutorial showing how that works.
> 
> A problem I encounter is how this can be used with type hierarchies i.e. 
> I have a smart pointer managing a tagged type, and I want to be able to 
> derive from that tagged type and still be able to use my smart pointer 
> with that new type. Let me give an example: Assume I want to implement 
> an abstract type Stream that represents a stream of events (has nothing 
> to do with Ada.Streams). I will use a slightly modified Rosen '95 name 
> scheme here for clarity: Reference is the smart pointer, Instance is the 
> actual object. Let this be the base type:
> 
<snip>
> 
> Some observations:
> 
> * Unless all implementations are child classes of Stream, it is 
> necessary to make the Instance type public.

Obv.

> * A derived type, if it wants to provide additional operations (like 
> Current_Position), must not only derive from Instance, but also from 
> Reference, to be able to provide an type-safe interface to those 
> operations.

Why?  All ops on Instance-derived types (including constructing 
subprograms) should be in terms of that type.  References are for access 
only (ho ho).

> * As types derived from Stream possibly need to derive Stream.Reference, 
> a consumer of a Stream object needs to take a Stream.Reference'Class as 
> input. This type cannot be used for a record field, so I need to 
> allocate it in heap memory and store a pointer if I want to memorize a 
> Stream.Reference value anywhere.

No.  This way lies madness.  A parallel hierarchy of References gains 
you very little and takes a lot of maintenance.

> * The implementation of Current_Position is cumbersome as I need the 
> Implementation_Access function and convert the result to 
> File_Stream.Instance, which creates a needless downcast check.

The downcast has to go *somewhere*.

<snip generic version>

I had to do this kind of thing a great deal in the Ada version of the 
mal lisp interpreter.  See for example:

https://github.com/zmower/mal/blob/master/ada/smart_pointers.ads
https://github.com/zmower/mal/blob/master/ada/types.ads

But my advice is to avoid tagged types if you can.  They only make sense 
if your problem is wildly dynamic or you want to be lazy when thinking 
about memory allocation.  mal qualified on both counts.


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
  2017-07-24 19:53 ` Dmitry A. Kazakov
  2017-07-24 21:24 ` Chris Moore
@ 2017-07-26 17:53 ` Ivan Levashev
  2017-07-28  9:21 ` AdaMagica
  2017-08-01  3:43 ` Randy Brukardt
  4 siblings, 0 replies; 15+ messages in thread
From: Ivan Levashev @ 2017-07-26 17:53 UTC (permalink / raw)


24.07.2017 22:41, Felix Krause пишет:
> * A derived type, if it wants to provide additional operations (like
> Current_Position), must not only derive from Instance, but also from
> Reference, to be able to provide an type-safe interface to those
> operations.

My approach is to have independent (mutually noncastable) reference 
types and explicit upcast/downcast functions.

You can either copy all parent's methods by hand or use generic mix-ins 
to add methods to proto-reference-types for every reference type.

Also, tricks with Ada 2012 references can save Adjust/Finally calls. As 
soon as references don't have dispatching, tag is useless, so it can 
overlap with Ada 2012 reference's access discriminant.

Best Regards,
Ivan Levashev,
Barnaul

--
If you want to get to the top, you have to start at the bottom

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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 19:53 ` Dmitry A. Kazakov
@ 2017-07-27 19:30   ` Felix Krause
  2017-07-27 20:42     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 15+ messages in thread
From: Felix Krause @ 2017-07-27 19:30 UTC (permalink / raw)


On 2017-07-24 19:53:54 +0000, Dmitry A. Kazakov said:

> On 2017-07-24 17:41, Felix Krause wrote:
> 
>> Now I am wondering what others think of these approaches. Are there 
>> alternatives? Which one would be better from a user perspective?
> 
> I am using generic pointer (Handle), which can be instantiated later 
> with any derived type from the base reference counting type. See Simple 
> Components:
> 
>     http://www.dmitry-kazakov.de/ada/components.htm#Objects_etc
> 
> This is your second approach but with object pointer type passed as a 
> second generic parameter.
> 
> generic
>     type Object_Type (<>) is abstract new Entity with private;
>     type Object_Type_Ptr is access Object_Type'Class;
> package Object.Handle is
>     type Handle is new Ada.Finalization.Controlled with private;
> 
> Users of smart pointer need not to be generic. I don't know why you 
> think it is necessary.

Using your types, let's say I have

    type Entity_Access is access Entity'Class;
    package Entity_Handle is new Object.Handle (Entity, Entity_Access);

    type A_Type is new Entity with private;
    type A_Access is access A_Type'Class;

    package A_Handle is new Object.Handle (A_Type, A_Access);

Now some subroutine wants to take an Entitiy as parameter, or more precisely, a
Handle to an Entity. If I specify this:

    procedure Do_Something (E : Entity_Handle.Handle);

I cannot call this procedure with an A_Handle.Handle. So I'd need to do:

    generic
       with package Actual_Handle is new Object.Handle (<>);
    procedure Do_Something (E : Actual_Handle.Handle);

I don't see how I can prevent this using your approach.

> Regarding hierarchy of types and additional operations and the problem 
> of exposing the implementation type. You can use delegation:
> 
> E.g. if you have your File_Stream_Object and File_Stream_Reference, you 
> define a File_Stream_Interface:
> 
>     type File_Stream_Interface is interface;
>     procedure Foo (Stream : in out File_Stream_Interface) is abstract;
> 
> Then both object and reference implement the interface:
> 
>     type File_Stream_Object is
>        new Instance and File_Stream_Interface ...
>     overriding procedure Foo (Stream : in out File_Stream_Reference);
> 
>     type File_Stream_Reference is
>        new Reference and File_Stream_Interface ...
>     overriding procedure Foo (Stream : in out File_Stream_Reference);
> 
>  From second Foo you call the first after dereferencing. Now you can 
> hide File_Stream_Object and expose only File_Stream_Interface and 
> File_Stream_Reference. You can also derive from File_Stream_Object and 
> File_Stream_Reference adding new interfaces. The latter has a drawback 
> that you will have to convert pointer to a more specific class.
> 
> ------------------------------------
> It is quite tedious because:
> 
> 1. Delegation cannot be automated in Ada;
> 2. Pointer are not promoted upon inheritance.
> 
> Otherwise this schema works well.

Thanks for this. It is probably not the approach I will use, but not a 
bad idea.

Another question using generics: If I do use a generic package for the 
smart pointers, is there a good way to put the instance of that package 
inside the package that defines the derived type?

Example (starts with the generic code in my original post):

    package Stream is
       type Instance is abstract limited tagged private;

       --  fetches an event from the stream
       procedure Fetch (Object : in out Instance; Ret : out Event) is abstract;
    private
       type Instance is abstract tagged limited record
          Refcount : Natural := 1;
       end record;
    end Stream;

    generic
       type Implementation is new Stream.Instance with private;
    package Stream.Smart is
       type Reference is new Ada.Finalization.Controlled with private;

       --  reference-counting implementation
       overriding procedure Adjust (Object : in out Reference);
       overriding procedure Finalize (Object : in out Reference);
    private
       type Implementation_Access is access all Implementation'Class;

       type Reference is new Ada.Finalization.Controlled with record
          Data : access Implementation_Access;
       end record;
    end Stream.Smart;

    package File_Stream is
       type Instance is new Stream.Instance with private;

       package Smart is new Stream.Smart (Instance);
    private
       type Instance is new Stream.Instance with record
          File : Ada.Text_IO.File_Access;
       end record;
    end File_Stream;

This does not compile because I cannot instantiate Stream.Smart with 
the incomplete File_Stream.Instance type. What would work is to have 
instead a child package:

    package File_Stream.Smart is new Stream.Smart (File_Stream.Instance);

But with this, I am not able to define any subroutines in the 
File_Stream package that take a smart pointer.

-- 
Regards,
Felix Krause


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 21:24 ` Chris Moore
@ 2017-07-27 19:38   ` Felix Krause
  2017-08-01  4:07     ` Randy Brukardt
  0 siblings, 1 reply; 15+ messages in thread
From: Felix Krause @ 2017-07-27 19:38 UTC (permalink / raw)


On 2017-07-24 21:24:58 +0000, Chris Moore said:

>> * A derived type, if it wants to provide additional operations (like 
>> Current_Position), must not only derive from Instance, but also from 
>> Reference, to be able to provide an type-safe interface to those 
>> operations.
> 
> Why?  All ops on Instance-derived types (including constructing 
> subprograms) should be in terms of that type.  References are for 
> access only (ho ho).

Well, if the smart pointer for File_Stream is the same type as the 
smart pointer for Stream, I'd need to downcast the retrieved access 
type each time I want to call a subroutine only defined for 
File_Stream, such as Current_Position in my example. An explicit 
downcast is undesirable because it implies that it may fail, which it 
cannot when I create a File_Stream reference with a construction 
subroutine and subsequently call Current_Position on it.

> I had to do this kind of thing a great deal in the Ada version of the 
> mal lisp interpreter.  See for example:
> 
> https://github.com/zmower/mal/blob/master/ada/smart_pointers.ads
> https://github.com/zmower/mal/blob/master/ada/types.ads
> 
> But my advice is to avoid tagged types if you can.  They only make 
> sense if your problem is wildly dynamic or you want to be lazy when 
> thinking about memory allocation.  mal qualified on both counts.

The sad thing is that I really like the prefix notation, which drags me 
towards tagged types, even though I don't like dispatching that much. 
Also, controlled types are tagged types, so it is hard to avoid them.

-- 
Regards,
Felix Krause

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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-27 19:30   ` Felix Krause
@ 2017-07-27 20:42     ` Dmitry A. Kazakov
  0 siblings, 0 replies; 15+ messages in thread
From: Dmitry A. Kazakov @ 2017-07-27 20:42 UTC (permalink / raw)


On 2017-07-27 21:30, Felix Krause wrote:
> On 2017-07-24 19:53:54 +0000, Dmitry A. Kazakov said:
> 
>> On 2017-07-24 17:41, Felix Krause wrote:
>>
>>> Now I am wondering what others think of these approaches. Are there 
>>> alternatives? Which one would be better from a user perspective?
>>
>> I am using generic pointer (Handle), which can be instantiated later 
>> with any derived type from the base reference counting type. See 
>> Simple Components:
>>
>>     http://www.dmitry-kazakov.de/ada/components.htm#Objects_etc
>>
>> This is your second approach but with object pointer type passed as a 
>> second generic parameter.
>>
>> generic
>>     type Object_Type (<>) is abstract new Entity with private;
>>     type Object_Type_Ptr is access Object_Type'Class;
>> package Object.Handle is
>>     type Handle is new Ada.Finalization.Controlled with private;
>>
>> Users of smart pointer need not to be generic. I don't know why you 
>> think it is necessary.
> 
> Using your types, let's say I have
> 
>     type Entity_Access is access Entity'Class;
>     package Entity_Handle is new Object.Handle (Entity, Entity_Access);
> 
>     type A_Type is new Entity with private;
>     type A_Access is access A_Type'Class;
> 
>     package A_Handle is new Object.Handle (A_Type, A_Access);
> 
> Now some subroutine wants to take an Entitiy as parameter, or more 
> precisely, a Handle to an Entity. If I specify this:
> 
>     procedure Do_Something (E : Entity_Handle.Handle);
> 
> I cannot call this procedure with an A_Handle.Handle. So I'd need to do:
> 
>     generic
>        with package Actual_Handle is new Object.Handle (<>);
>     procedure Do_Something (E : Actual_Handle.Handle);
> 
> I don't see how I can prevent this using your approach.

Assuming that you are not going to derive from Entity_Handle.Handle, you 
have two options:

1. Provide a conversion

    function "-" (A : A_Handle.Handle) return Entity_Handle.Handle'Class;

Then you can Do_Something (-A).

2. Provide a wrapper. Define Do_Something on A_Handle.Handle and call 
original the Do_Something from inside.

> Another question using generics: If I do use a generic package for the 
> smart pointers, is there a good way to put the instance of that package 
> inside the package that defines the derived type?

There is no good way, but it is not critical because if you are going 
smart pointers you also should hide objects they point to. Therefore 
they must go into separate packages and there will be no public cross 
operations in the interface packages, only factories.

P.S. I don't place object types in private packages, because most likely 
it will leak this way or another. So I just object type packages public 
but mark as an implementation.

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


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
                   ` (2 preceding siblings ...)
  2017-07-26 17:53 ` Ivan Levashev
@ 2017-07-28  9:21 ` AdaMagica
  2017-07-30 19:45   ` briot.emmanuel
  2017-08-01  3:43 ` Randy Brukardt
  4 siblings, 1 reply; 15+ messages in thread
From: AdaMagica @ 2017-07-28  9:21 UTC (permalink / raw)


I haven't followed the thread in detail, but perhaps this page can help you:

http://www.christ-usch-grein.homepage.t-online.de/Ada/Smart_Pointers.html


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-28  9:21 ` AdaMagica
@ 2017-07-30 19:45   ` briot.emmanuel
  0 siblings, 0 replies; 15+ messages in thread
From: briot.emmanuel @ 2017-07-30 19:45 UTC (permalink / raw)


I like the approach with Implicit_Dereference for the accessor In AdaMagica's link. In fact, I am pretty sure we could use this for the smart pointer itself, so that the call to Get at the bottom of the page is not even necessary. I started playing with that for GNATCOLL.Refcount, but did not have time to finish yet.


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
                   ` (3 preceding siblings ...)
  2017-07-28  9:21 ` AdaMagica
@ 2017-08-01  3:43 ` Randy Brukardt
  2017-08-01  7:36   ` Dmitry A. Kazakov
  4 siblings, 1 reply; 15+ messages in thread
From: Randy Brukardt @ 2017-08-01  3:43 UTC (permalink / raw)


"Felix Krause" <contact@flyx.org> wrote in message 
news:2017072417413775878-contact@flyx.org...
...
> A problem I encounter is how this can be used with type hierarchies i.e. I 
> have a smart pointer managing a tagged type, and I want to be able to 
> derive from that tagged type and still be able to use my smart pointer 
> with that new type.

What's needed is something like "co-derivation", where multiple types are 
derived in lock-step. Sadly, this is effectively impossible in an OOP 
environment, because dispatching calls would have the wrong parameter types 
for any co-derived types. (I've spent quite a bit of effort in trying to 
make such a solution work, and have enlisted others to help, but the 
conclusion was that it wasn't promising.)

One would have to have some form of multiple dispatch to get around that, 
but that's a bridge too far for a language that's mainly used in embedded, 
safety-critical systems.

One could make it work for non-tagged types, but of course that prevents 
dispatching and extension. So it's probably not worth the effort to design.

                                       Randy.



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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-07-27 19:38   ` Felix Krause
@ 2017-08-01  4:07     ` Randy Brukardt
  0 siblings, 0 replies; 15+ messages in thread
From: Randy Brukardt @ 2017-08-01  4:07 UTC (permalink / raw)


"Felix Krause" <contact@flyx.org> wrote in message 
news:2017072721383428219-contact@flyx.org...
> On 2017-07-24 21:24:58 +0000, Chris Moore said:
>
>>> * A derived type, if it wants to provide additional operations (like 
>>> Current_Position), must not only derive from Instance, but also from 
>>> Reference, to be able to provide an type-safe interface to those 
>>> operations.
>>
>> Why?  All ops on Instance-derived types (including constructing 
>> subprograms) should be in terms of that type.  References are for access 
>> only (ho ho).
>
> Well, if the smart pointer for File_Stream is the same type as the smart 
> pointer for Stream, I'd need to downcast the retrieved access type each 
> time I want to call a subroutine only defined for File_Stream, such as 
> Current_Position in my example. An explicit downcast is undesirable 
> because it implies that it may fail, which it cannot when I create a 
> File_Stream reference with a construction subroutine and subsequently call 
> Current_Position on it.

Our experience with Claw eventually led to an in-house rule that all access 
types (and pseudo-access-types like smart pointers would be the same) had to 
be access-to-classwide. Once we did that, we found that we could avoid most 
explicit type conversions by using dispatching; we did need a few in cases 
of operations only defined for a subset of child types.

<Rant>There are no "casts" in Ada. Moreover, talking about "up" or "down" 
when talking about type conversions is confusing, mainly because many 
computer people apparently have never seen an actual tree. I can speak from 
experience when I say that I've seen thousands of trees and almost every one 
of them had the roots on the bottom. (The rest were windfalls and had the 
roots on the side...:-) Ergo, "down" in a tree is inherently toward the 
roots. Since a lot of people like to draw their trees upside down, they're 
confused about where the root of a tree is. The only solution to that is to 
be explicit: convert toward the root or toward the leaves. The only thing a 
"downcast" should be used for is fly fishing (and I *hate* fishing :-). If 
you want people to understand, "cast" out that terminology.</Rant>

                                                    Randy.



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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-08-01  3:43 ` Randy Brukardt
@ 2017-08-01  7:36   ` Dmitry A. Kazakov
  2017-08-01 22:41     ` Randy Brukardt
  0 siblings, 1 reply; 15+ messages in thread
From: Dmitry A. Kazakov @ 2017-08-01  7:36 UTC (permalink / raw)


On 2017-08-01 05:43, Randy Brukardt wrote:
> "Felix Krause" <contact@flyx.org> wrote in message
> news:2017072417413775878-contact@flyx.org...
> ...
>> A problem I encounter is how this can be used with type hierarchies i.e. I
>> have a smart pointer managing a tagged type, and I want to be able to
>> derive from that tagged type and still be able to use my smart pointer
>> with that new type.
> 
> What's needed is something like "co-derivation", where multiple types are
> derived in lock-step. Sadly, this is effectively impossible in an OOP
> environment, because dispatching calls would have the wrong parameter types
> for any co-derived types. (I've spent quite a bit of effort in trying to
> make such a solution work, and have enlisted others to help, but the
> conclusion was that it wasn't promising.)
> 
> One would have to have some form of multiple dispatch to get around that,
> but that's a bridge too far for a language that's mainly used in embedded,
> safety-critical systems.

But Ada already has this form of multiple-dispatch. The issue does not 
requires two types. Two controlling parameters of the same type is just 
enough:

    type T is tagged ...
    procedure Foo (X, Y : T);

    type S is new T with ...
    overriding procedure Foo (X, Y : S);

    X : T'Class := T'(...);
    Y : T'Class := S'(...);
begin
    Foo (X, Y);

So I would not be much concerned about that.

Specifically for smart pointers we also need sliding access to co-type 
upon derivation. One could think of it as a constraint:

    type Pointer_Type (Target_Class : Tag ???) is record
       Ptr : access Target_Type'Class with Subclass => Target_Class;
    end record;

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


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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-08-01  7:36   ` Dmitry A. Kazakov
@ 2017-08-01 22:41     ` Randy Brukardt
  2017-08-02  6:28       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 15+ messages in thread
From: Randy Brukardt @ 2017-08-01 22:41 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:olpb12$1o3$1@gioia.aioe.org...
> On 2017-08-01 05:43, Randy Brukardt wrote:
...
>> One would have to have some form of multiple dispatch to get around that,
>> but that's a bridge too far for a language that's mainly used in 
>> embedded,
>> safety-critical systems.
>
> But Ada already has this form of multiple-dispatch. The issue does not 
> requires two types. Two controlling parameters of the same type is just 
> enough:
>
>    type T is tagged ...
>    procedure Foo (X, Y : T);
>
>    type S is new T with ...
>    overriding procedure Foo (X, Y : S);
>
>    X : T'Class := T'(...);
>    Y : T'Class := S'(...);
> begin
>    Foo (X, Y);
>
> So I would not be much concerned about that.

This is described (and implemented) as a single dispatch with a tag equality 
check. Nothing multiple about it outside of the initial appearance.

To use that mechanism implies that you have only one type (and a shared tag) 
as opposed to two different but related types. I'm not sure how that could 
work, given the very different properties of a reference vs. an object type. 
It also would require some relaxation of the "one controlling type per 
primitive subprogram" rule. I suspect both of those would run into trouble 
with the generic contract model (specifically with generic derived types), 
but someone would have to try to work out all of the details in order to be 
sure.

                                 Randy.




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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-08-01 22:41     ` Randy Brukardt
@ 2017-08-02  6:28       ` Dmitry A. Kazakov
  2017-08-02 19:26         ` Randy Brukardt
  0 siblings, 1 reply; 15+ messages in thread
From: Dmitry A. Kazakov @ 2017-08-02  6:28 UTC (permalink / raw)


On 2017-08-02 00:41, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:olpb12$1o3$1@gioia.aioe.org...
>> On 2017-08-01 05:43, Randy Brukardt wrote:
> ...
>>> One would have to have some form of multiple dispatch to get around that,
>>> but that's a bridge too far for a language that's mainly used in
>>> embedded,
>>> safety-critical systems.
>>
>> But Ada already has this form of multiple-dispatch. The issue does not
>> requires two types. Two controlling parameters of the same type is just
>> enough:
>>
>>     type T is tagged ...
>>     procedure Foo (X, Y : T);
>>
>>     type S is new T with ...
>>     overriding procedure Foo (X, Y : S);
>>
>>     X : T'Class := T'(...);
>>     Y : T'Class := S'(...);
>> begin
>>     Foo (X, Y);
>>
>> So I would not be much concerned about that.
> 
> This is described (and implemented) as a single dispatch with a tag equality
> check. Nothing multiple about it outside of the initial appearance.

Except that the dispatch table that should have been 2-D:

    T1 T2 T3
T1  *
T2     *
T2        *

You is flatten to 1-D:

   T1 T2 T3

The check is to ensure that you are indeed on the diagonal.

> To use that mechanism implies that you have only one type (and a shared tag)
> as opposed to two different but related types. I'm not sure how that could
> work, given the very different properties of a reference vs. an object type.
> It also would require some relaxation of the "one controlling type per
> primitive subprogram" rule. I suspect both of those would run into trouble
> with the generic contract model (specifically with generic derived types),
> but someone would have to try to work out all of the details in order to be
> sure.

Same as above. The 2-D table is:

    A1 A2 A3
B1  *
B2     *
B3        *

You do exactly same thing. You check if you are on the diagonal, then 
you dispatch using any of two tags once. Just one hop.

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

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

* Re: Smart Pointers and Tagged Type Hierarchies
  2017-08-02  6:28       ` Dmitry A. Kazakov
@ 2017-08-02 19:26         ` Randy Brukardt
  0 siblings, 0 replies; 15+ messages in thread
From: Randy Brukardt @ 2017-08-02 19:26 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:olrreq$1tp3$1@gioia.aioe.org...
> On 2017-08-02 00:41, Randy Brukardt wrote:
>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
>> news:olpb12$1o3$1@gioia.aioe.org...
>>> On 2017-08-01 05:43, Randy Brukardt wrote:
>> ...
...
> Same as above. The 2-D table is:
>
>    A1 A2 A3
> B1  *
> B2     *
> B3        *
>
> You do exactly same thing. You check if you are on the diagonal, then you 
> dispatch using any of two tags once. Just one hop.

That's how interfaces work, essentially. They are very difficult to 
implement efficiently (I believe it is impossible in general). I 
(personally) don't want more interface like implementation complication. 
(It's unlikely that Janus/Ada will ever implement interfaces for this 
reason.)

                                  Randy.


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

end of thread, other threads:[~2017-08-02 19:26 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-07-24 15:41 Smart Pointers and Tagged Type Hierarchies Felix Krause
2017-07-24 19:53 ` Dmitry A. Kazakov
2017-07-27 19:30   ` Felix Krause
2017-07-27 20:42     ` Dmitry A. Kazakov
2017-07-24 21:24 ` Chris Moore
2017-07-27 19:38   ` Felix Krause
2017-08-01  4:07     ` Randy Brukardt
2017-07-26 17:53 ` Ivan Levashev
2017-07-28  9:21 ` AdaMagica
2017-07-30 19:45   ` briot.emmanuel
2017-08-01  3:43 ` Randy Brukardt
2017-08-01  7:36   ` Dmitry A. Kazakov
2017-08-01 22:41     ` Randy Brukardt
2017-08-02  6:28       ` Dmitry A. Kazakov
2017-08-02 19:26         ` Randy Brukardt

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