* 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