• Best use cases for OOP

    From Kevin Chadwick@kc-usenet@chadwicks.me.uk to comp.lang.ada on Thu Aug 28 14:40:57 2025
    From Newsgroup: comp.lang.ada

    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
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Kevin Chadwick@kc-usenet@chadwicks.me.uk to comp.lang.ada on Thu Aug 28 14:45:09 2025
    From Newsgroup: comp.lang.ada

    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
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Dmitry A. Kazakov@mailbox@dmitry-kazakov.de to comp.lang.ada on Thu Aug 28 17:12:22 2025
    From Newsgroup: comp.lang.ada

    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
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Niklas Holsti@niklas.holsti@tidorum.invalid to comp.lang.ada on Thu Aug 28 21:28:13 2025
    From Newsgroup: comp.lang.ada

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Alex // nytpu@nytpu@example.invalid to comp.lang.ada on Thu Aug 28 14:23:58 2025
    From Newsgroup: comp.lang.ada

    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/
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Dmitry A. Kazakov@mailbox@dmitry-kazakov.de to comp.lang.ada on Fri Aug 29 10:28:24 2025
    From Newsgroup: comp.lang.ada

    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
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Kevin Chadwick@kc-usenet@chadwicks.me.uk to comp.lang.ada on Fri Aug 29 09:14:19 2025
    From Newsgroup: comp.lang.ada


    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
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Dmitry A. Kazakov@mailbox@dmitry-kazakov.de to comp.lang.ada on Fri Aug 29 12:37:24 2025
    From Newsgroup: comp.lang.ada

    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
    --- Synchronet 3.21a-Linux NewsLink 1.2