comp.lang.ada
 help / color / mirror / Atom feed
* Best use cases for OOP
@ 2025-08-28 14:40 Kevin Chadwick
  2025-08-28 14:45 ` Best use cases for OOP (resent quoted sorry) Kevin Chadwick
  0 siblings, 1 reply; 8+ messages in thread
From: Kevin Chadwick @ 2025-08-28 14:40 UTC (permalink / raw)


On 28/08/2025 10:02, Dmitry A. Kazakov wrote:

On 2025-08-28 10:50, Kevin Chadwick wrote:

I can explain how it works. When a DLL is loaded its library level

gets elaborated. So if you have:

package P is

   type T is tagged ...

end P;

in the main program or DLL and

with P;

package Q is

   type S is new T with ...

   function F return T'Class;

end Q;

in the DLL being loaded, then in the process of elaboration S will be

created which in particular means extending of the dispatching table.

You can return a class-wide object from there and dispatch on the

newly created type's operation.

Clear?

Interesting, better than the shape examples but I guess you have to get

that in the first instance.

Dynamic polymorphism is an extremely powerful mechanism, but it kind of

bends out of traditional typing.

It is like the relativity theory. Everything is relative, position,

velocity, but up to the point. Acceleration is not anymore. Same is with

typing: value->type, so far so good, but one more step up:

value->type->class and something happens. Primitive operations become

reachable even if you do not see them, you cannot hide them. Multiple

inheritance, OK, but what about conflicts, is it additive or idempotent?

Multiple dispatch is a total dark hole.


I have struggled to find compelling reasons to use tagged types considering
they affect the size of records and potentially elaboration issues that
cannot exist without tagged types.

I know some use tagged types just for dot notation but that doesn't really
move the needle in my mind. I would certainly appreciate it if people could
share their favourite use cases for tagged types though. Things that are
perhaps a pain without them or were with Ada 83.

-- 
Regards, Kc

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-28 14:40 Best use cases for OOP Kevin Chadwick
@ 2025-08-28 14:45 ` Kevin Chadwick
  2025-08-28 15:12   ` Dmitry A. Kazakov
                     ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Kevin Chadwick @ 2025-08-28 14:45 UTC (permalink / raw)


On 28/08/2025 15:40, Kevin Chadwick wrote:
>On 28/08/2025 10:02, Dmitry A. Kazakov wrote:
>
>On 2025-08-28 10:50, Kevin Chadwick wrote:
>
>I can explain how it works. When a DLL is loaded its library level
>
>gets elaborated. So if you have:
>
>package P is
>
>   type T is tagged ...
>
>end P;
>
>in the main program or DLL and
>
>with P;
>
>package Q is
>
>   type S is new T with ...
>
>   function F return T'Class;
>
>end Q;
>
>in the DLL being loaded, then in the process of elaboration S will be
>
>created which in particular means extending of the dispatching table.
>
>You can return a class-wide object from there and dispatch on the
>
>newly created type's operation.
>
>Clear?
>
>Interesting, better than the shape examples but I guess you have to get
>
>that in the first instance.
>
>Dynamic polymorphism is an extremely powerful mechanism, but it kind of
>
>bends out of traditional typing.
>
>It is like the relativity theory. Everything is relative, position,
>
>velocity, but up to the point. Acceleration is not anymore. Same is with
>
>typing: value->type, so far so good, but one more step up:
>
>value->type->class and something happens. Primitive operations become
>
>reachable even if you do not see them, you cannot hide them. Multiple
>
>inheritance, OK, but what about conflicts, is it additive or idempotent?
>
>Multiple dispatch is a total dark hole.
>
>

I have struggled to find compelling reasons to use tagged types considering
they affect the size of records and potentially elaboration issues that
cannot exist without tagged types.

I know some use tagged types just for dot notation but that doesn't really
move the needle in my mind. I would certainly appreciate it if people could
share their favourite use cases for tagged types though. Things that are
perhaps a pain without them or were with Ada 83.


-- 
Regards, Kc

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-28 14:45 ` Best use cases for OOP (resent quoted sorry) Kevin Chadwick
@ 2025-08-28 15:12   ` Dmitry A. Kazakov
  2025-08-28 18:28   ` Niklas Holsti
  2025-08-28 20:23   ` Alex // nytpu
  2 siblings, 0 replies; 8+ messages in thread
From: Dmitry A. Kazakov @ 2025-08-28 15:12 UTC (permalink / raw)


On 2025-08-28 16:45, Kevin Chadwick wrote:

> I have struggled to find compelling reasons to use tagged types considering
> they affect the size of records and potentially elaboration issues that
> cannot exist without tagged types.

Nothing forces you to use variant sizes or construction hooks. The 
advantage is the you can, if you need it.

> I know some use tagged types just for dot notation but that doesn't really
> move the needle in my mind. I would certainly appreciate it if people could
> share their favourite use cases for tagged types though. Things that are
> perhaps a pain without them or were with Ada 83.

1. The easiest example is network protocols. If you take a look at the 
Simple Components implementation, then the server and client know 
nothing about the actual protocol. They simply read and write chunk of 
data. Upon connection a factory is called which returns an instance of a 
connection object. This type implements HTTP, MQTT, ModBus, LDAP 
whatever. The server/client simply passes data to the object for 
processing. That are dispatching calls.

2. Syntax tree. The nodes of the tree are tagged types. You can nicely 
reuse parts of implementation, e.g. numeric literals etc. You can 
inspect an implementation of Ada expression parser. The parser itself 
knows nothing about Ada. It is the same parser that is used for JSON or XPM.

3. GUI widgets it are practically impossible to have without OO, as you 
need a hierarchy of and an ability to create new widgets using the old 
ones. In AICWL layers of a gauge, oscilloscope, meter are tagged types. 
The instrument is drawn by drawing each layer via a dispatching call.

4. Practically all container types are tagged in order to extend 
functionality but also for generic programming. Consider an ability to 
iterate a container. All iteratable containers form a class. With tagged 
types you can formally define such a class as an interface and inherit 
it in vector, map, set. You can do that with generics too, but that 
would force everything generic.

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

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-28 14:45 ` Best use cases for OOP (resent quoted sorry) Kevin Chadwick
  2025-08-28 15:12   ` Dmitry A. Kazakov
@ 2025-08-28 18:28   ` Niklas Holsti
  2025-08-28 20:23   ` Alex // nytpu
  2 siblings, 0 replies; 8+ messages in thread
From: Niklas Holsti @ 2025-08-28 18:28 UTC (permalink / raw)


On 2025-08-28 17:45, Kevin Chadwick wrote:

    [ snip ]

> I have struggled to find compelling reasons to use tagged types considering
> they affect the size of records and potentially elaboration issues that
> cannot exist without tagged types.
> 
> I know some use tagged types just for dot notation but that doesn't really
> move the needle in my mind.

An Ada amendment (AI22-0091-1) that extends dot notation to almost all 
untagged types has been approved for the next Ada standard:

http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai22s/ai22-0091-1.html?rev=Top1.8

> I would certainly appreciate it if people could share their favourite
> use cases for tagged types though. Things that are perhaps a pain
> without them or were with Ada 83.
Rather than a use-case, here is a general argument (well known already): 
in principle a tagged type T could be replaced by an Ada 83 record type 
with an enumerated discriminant (corresponding to the tag of T) and 
corresponding variants (corresponding to the types in T'Class). 
Dynamically dispatched operation calls would be replaced by "case" 
statements indexed by the discriminant.

However, that would make it much harder to structure and maintain the 
program, because:

1. The declaration of the discriminant enumeration must contain all 
values that will be used anywhere in the program. This central 
declaration must be visible to all uses of the discriminant.

2. Every "case" statement must contain a "when" branch for every value 
of the discriminant. Thus every "case" statement has to handle all 
variants and have visibility on the modules that implement the logic for 
each variant. This leads to a lot of inter-module dependencies across 
variants that may be quite unrelated.

3. Consequently, adding or removing a variant (corresponding to adding 
or removing a type in T'Class) implies changes to many source files and 
requires a lot of recompilation and perhaps much retesting.

In contrast, when a tagged type is used, instead of a record with variants:

A. There is no central declaration of all derived types (all types in 
T'Class). Each such type can be declared and be visible only in the 
modules where it is relevant. Only the root type T must be widely visible.

B. The implementation of each operation on the class is not a "case" 
statement with a "when" branch for each derived type. Instead, thru 
dynamic dispatch, the implementations of the operations for a derived 
type can be placed in the modules relevant to that type, as if the 
"case" statements had been cut up to collect the "when" branches for 
each variant into a module for that variant.

C. Consequently, adding or removing a derived type in T'Class is usually 
a very local change to a closely related set of source files. Adding or 
removing primitive operations of T can still require changes to many 
source files (the files implementing each derived type), but that can be 
mitigated by creating subclasses of T'Class so that operations 
applicable to a subclass (a subset of the types in T'Class) are defined 
only in that subclass and not all T'Class.

In summary, tagged types provide a new and IMO powerful and useful way 
to modularize a program, separate concerns, and reduce unnecessary 
inter-module dependencies.

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-28 14:45 ` Best use cases for OOP (resent quoted sorry) Kevin Chadwick
  2025-08-28 15:12   ` Dmitry A. Kazakov
  2025-08-28 18:28   ` Niklas Holsti
@ 2025-08-28 20:23   ` Alex // nytpu
  2025-08-29  8:28     ` Dmitry A. Kazakov
  2 siblings, 1 reply; 8+ messages in thread
From: Alex // nytpu @ 2025-08-28 20:23 UTC (permalink / raw)


On 8/28/25 8:45 AM, Kevin Chadwick wrote:
> <...>
> 
> I have struggled to find compelling reasons to use tagged types considering
> they affect the size of records and potentially elaboration issues that
> cannot exist without tagged types.
> 
> I know some use tagged types just for dot notation but that doesn't really
> move the needle in my mind. I would certainly appreciate it if people could
> share their favourite use cases for tagged types though. Things that are
> perhaps a pain without them or were with Ada 83.
In general, things like variant records are sorta "perpendicular" in use 
to OOP/tagged types.  Tagged types make it easy to add a new class while 
not having to modify most of the routines you can use on the class, but 
it's tedious to add new routines; while variant records make it easy to 
add new routines to use on the type but it's tedious to add a new record 
variant.  Linking to a random section of a random book but this section 
is a great explanation: 
<https://craftinginterpreters.com/representing-code.html#the-expression-problem>

In Ada in particular, my main preferred use-case is defining interfaces 
so my routines can take a `My_Interface'Class` type and then anything 
needing to use them can just implement the interface in whatever record 
the want to use my code with.  This seems to be one of the primary 
things the Ada standard library uses it for too, e.g. streams, storage 
pools, and controlled types.  Note that I'm saying "interface" as a 
concept distinct from Ada's interfaces, for instance the standard 
library makes controlled types and storage pools be full tagged types 
you subclass (for various complex reasons) but conceptually they're 
still just empty types defining a common interface for the routines 
those APIs need.

Although admittedly it is extremely rare I use (non-derived) tagged 
types at all though, and when I do it is almost exclusively to get dot 
notation lol (and tagged types were supposed to no longer be needed for 
dot calls in Ada 2022 but it was delayed until Ada 202y).  I do use 
types derived from the standard library types a lot though; namely 
controlled types for bindings and for custom stream types.

I wouldn't really worry about the "overhead" of tagged types not 
significant on any modern system and no more than a language like C++ 
(let alone high-level dynamic languages) add to literally every data 
structure with no option to omit the metadata like Ada has.  The only 
real issue is that IIRC tagged types are always at least twenty bytes on 
GNAT (on ARMv4T/ARMv5T at least, since developing for them is the only 
time I've gotten warnings about this limitation) which precludes things 
like having a record be small enough to be passed in a register instead 
of the stack, but not much of an issue on modern systems.  It'd also 
preclude things like a record containing a single field being optimized 
to acting like a variable directly holding that field type since there's 
the tag overhead.  Very premature micro-optimization-y concern still 
though IMO, just use tagged types when it makes sense.

~nytpu

-- 
Alex // nytpu
https://nytpu.com/ - gemini://nytpu.com/ - gopher://nytpu.com/

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-28 20:23   ` Alex // nytpu
@ 2025-08-29  8:28     ` Dmitry A. Kazakov
  2025-08-29  9:14       ` Kevin Chadwick
  0 siblings, 1 reply; 8+ messages in thread
From: Dmitry A. Kazakov @ 2025-08-29  8:28 UTC (permalink / raw)


On 2025-08-28 22:23, Alex // nytpu wrote:

> The only 
> real issue is that IIRC tagged types are always at least twenty bytes on 
> GNAT (on ARMv4T/ARMv5T at least, since developing for them is the only 
> time I've gotten warnings about this limitation) which precludes things 
> like having a record be small enough to be passed in a register instead 
> of the stack, but not much of an issue on modern systems.

The reason is that tagged types are by-reference types. And that is 
because one wanted to have T'Class <-> T conversions a mere view 
conversions.

One language extension I always wanted were an ability to have classes 
of types without having a tag embedded in the representation. So that 
one could have Boolean'Class. In this model T'Class <-> T would be a 
full conversion producing a new object. The types must be by-value then. 
Furthermore for such types re-dispatch would be impossible:

    procedure Foo (X : in out T) is
    begin
       T'Class (X).Bar; -- Error, T'Class (X) is a new object

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

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-29  8:28     ` Dmitry A. Kazakov
@ 2025-08-29  9:14       ` Kevin Chadwick
  2025-08-29 10:37         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 8+ messages in thread
From: Kevin Chadwick @ 2025-08-29  9:14 UTC (permalink / raw)



>One language extension I always wanted were an ability to have classes 
>of types without having a tag embedded in the representation.

I understand the usability problems tags can cause but not sure I understand
 the benefit. Would it allow memory mapped registers such as Timer_1 and
 Timer_2 with some differences but whose shared components could be handled
 by a procedure accepting 'Class even though those shared components were
 memory mapped to different locations. If so I would love that. Even if it
 would not enable that. Have you considered making a suggestion here?

"https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues"


-- 
Regards, Kc

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

* Re: Best use cases for OOP (resent quoted sorry)
  2025-08-29  9:14       ` Kevin Chadwick
@ 2025-08-29 10:37         ` Dmitry A. Kazakov
  0 siblings, 0 replies; 8+ messages in thread
From: Dmitry A. Kazakov @ 2025-08-29 10:37 UTC (permalink / raw)


On 2025-08-29 11:14, Kevin Chadwick wrote:
> 
>> One language extension I always wanted were an ability to have classes 
>> of types without having a tag embedded in the representation.
> 
> I understand the usability problems tags can cause but not sure I 
> understand
> the benefit. Would it allow memory mapped registers such as Timer_1 and
> Timer_2 with some differences but whose shared components could be handled
> by a procedure accepting 'Class even though those shared components were
> memory mapped to different locations.

Your Timer_1 and Timer_2 are limited types, so you cannot have a class 
of them without burying a tag into. Since you cannot copy such things 
you cannot convert Timer_1 to Timer'Class per view. Unless you allow 
copy-in copy-out semantics.

> If so I would love that. Even if it
> would not enable that. Have you considered making a suggestion here?
> 
> "https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues"

<rant on>

No, because such changes are never accepted. One thing is to introduce 
square brackets and break separation of interface and implementation 
(all these bodies of functions in declarations, 
if-/case-/for-expressions etc) and another thing is a serious overhaul 
of the type system such as it was done in Ada 95.

Note that it is more than just handling external tags. It is a general 
principle of a type constraint. Tag is a constraint of T'Class. So are 
array bounds. So is dimension in the AdaCore hack of dimensioned types. 
All these should have been discriminants. And all discriminants should 
have been handled like array bounds are handled. That is when you 
declare a constrained array String (1..80), the bounds are not there. 
When you make it unconstrained (convert to String) the bounds are added 
(e.g. passed around). Now compare that with a record discriminant, with 
a type tag in T <-> T'Class etc.

Though it would be quite possible to make this backward compatible, any 
proposal will be dismissed on the ground of "breaking" the language.

People think they hate OO, consider yourself. But it reality they hate 
any type system which is an inch more powerful than C. A miniscule 
minority could gritting the teeth accept Ada 83. But this is where the 
train stops.

<rant off>

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

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

end of thread, other threads:[~2025-08-29 10:37 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-28 14:40 Best use cases for OOP Kevin Chadwick
2025-08-28 14:45 ` Best use cases for OOP (resent quoted sorry) Kevin Chadwick
2025-08-28 15:12   ` Dmitry A. Kazakov
2025-08-28 18:28   ` Niklas Holsti
2025-08-28 20:23   ` Alex // nytpu
2025-08-29  8:28     ` Dmitry A. Kazakov
2025-08-29  9:14       ` Kevin Chadwick
2025-08-29 10:37         ` Dmitry A. Kazakov

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