How to inherit string handling?

I mean, it’s at the pre-model stage —that is, we need good models— so describe what you mean by “inheritance proper”. (I don’t think that ‘inheritance’ is related to the problem at all, unless you are referring to the ability to say “X is a Y, with these additional properties.”)

?
I think you’re thinking backwards to what the proposal is saying: an array would be an instance of that abstract interface for arrays, yes; but it would be invalid to have that abstraction (e.g.) as a parameter or as an object…like I said, “turning the formal parameters [of a generic] inside-out”: instead of being able to qualify and build types by static-polymorphism, allowing a type to reference/conform to the given interface.

So, while it would be fine to say Integer is a Discrete is an Any_type, and use Integer where appropriate, this isn’t necessarily like Interface: it’s static, for one, and requires no “buy-in” on dispatch or tagging.

That is one of the big motivating factors, that and how unwieldy it is to use user-defined indexing… the number of additional things you have to attend to for user-defined indexing is ridiculous.

What else it could be?

I want generics removed from the language. There must be no classes without class-wide objects.

You can also do it via traits (which are not not inheritance nor type extension) (NOTE: not the same as traits packages, different concept), though current Ada has no concept of this kind of implementation (some other languages do, Rust comes to mind off the top of my head).

A trait is essentially a way of expressing that a procedure can accept anything that provides an implementation that fulfills a specific contract. And that contract itself is a first class type of its own, passed in as a parameter. You can emulate this in Ada via interfaces (which do use inheritance, but inheritance is not a requirement to implement this in general, it’s just how Ada chose to do it).

Is-a is being a member of a class. You can turn it any way you want. It is always classes and properties of = interfaces. You inherit at least the interface.

Which means exactly: the procedure accepts members of a class of implementations of an interface.

Why?
Generics are quite well done.
I’d much rather remove dot-notation for objects (merely syntax) than generics; the proper way to achieve code-reuse is generics, not OOP.

That proposal has been described as a traits-system.

1 Like

Yes, but the point was you don’t have to do it through “is-a”, you can do it through “acts-like-a” which doesn’t require membership to a class. It can be a separate thing.

As I said, no classes without objects. I don’t want macro expansions and no metalanguage inside the language.

That generics (AKA macro expansions) do not support reuse is another argument against them. As an example write a program that prints any vector instantiated from the standard library. Put it in a DLL. When ready, talk about reuse! :sunglasses:

You cannot reuse something that does not exist in the object language. Generic object is not an object. Generic procedure is not a procedure. Generic type (inside a generic package) is not a type etc.

No. Acts-like-a is a member of the class of all things that act like a. As I said, there is simply no other way.

In Ada act-like-a is called interface. There is no implementation just definition of methods = acting things.

The point is always whether instances of the class are first-class citizens (no pun intended). Poor languages and paradigms (e.g. parametric polymorphism) limit class to a foot note in the reference manual, like integer class or instances of generics in Ada. There is a class but no class-wide objects.

That is a language bug. Integer is not a big loss as you cannot derive from integer type without cloning. Each time you do:

type I is new Integer;

you get a new integer class hierarchy rooted in I’Base with only one member inside. But if you tried to put several integer types into one class, e.g. having different implementation, you would not be able to do that.

Generics aren’t macro-expansions.
There are compilers that do shared generics.

The crippled shared-library notion stemming from OSes being written in C isn’t a good counter-example; IIUC, IBM’s SOM was capable of sharing on the generic-level. (IBM’s OS/2 was SOM capable, but did DLLS for compatibility with microsoft.)

There is some overlap, to be sure, but I rather agree: you can use the behaviors and “shape” of a thing, completely independent of any heiarchy.

1 Like

Of course you can macro optimize expansions by factoring out some code.

Cool, recompile and re-link the OS, all libraries, compilers, drivers each time you change your program! Even MS-DOS was better!

I think we can thus agree that generics are incompatible with any computing model dated later than 50’s of previous century! :rofl:

But you evaded the question. What about writing a vector print procedure?

It can be a member, and you may prefer it to be a member, but there is no requirement for it to be a member of the class. You can have “act-like-a” implementations that are in no way members of a class (see traits in Rust and similar languages).

In Ada, since interfaces are implemented via type extension/inheritance, then derived objects form an “is-a” relationship. It is definitely possible to implement interfaces as “act-like-a” but that wasn’t the implementation chosen by the ARG. In Ada if you implement an interface, that type “is” a child of that interface.

Note, I am not saying one way is better or worse than the other, just nothing there are alternatives.

That’s not what I said.
What I said was that the OS designs are crippled because (turing-completeness notwithstanding) such a feature is literally inexpressible in C.

Another concept that OSes don’t do, again because of the C influence, is subtype constraints — though here you can point to ASN.1 as a counterexample, thought it’s not on the OS-level.

I answered: DLLs are not capable exactly because the capability is inconceivable in C. — To have what you’re suggesting is doable, but with a library system that is more featureful than can be expressed in the world of DLLs (or SO for linux.

I get what you’re saying.

You defined a class by merely saying act-like-a.

Generic classes are implemented without type extension.

There is no alternatives to classes /= tagged types. When talking about classes please distinguish a concrete implementation, e.g. tagged type from a mathematical/logical concept of a set of types sharing some properties.

Tagged types is a very limited model of a set of types suffering many problems, from forcing implementation inheritance to lack of MI and MD. It does not mean that there may exist a paradigm alternative to class. It would be just a different way to implement class, usually even more inferior than tagged types, e.g. see generic classes or ad-hoc classes introduced by overloading.

Write procedure Print in Ada without putting it into a DLL!

P.S. The point about DLL was merely to indicate that generics do not support elementary means of reuse, modularisation, deployment and maintenance, like DLLs.

Once pre-processor is always pre-processor.

Generic
   Type Element(<>) is private;
   with package Vector is new Ada.Containers.Indefinite_Vectors
   (Element_Type => Element, others => <>);
   with Function  Image(Object : Element) return String;
   with Procedure Print(Object : String);
Procedure Generic_Print(Object : Vector.Vector);
Procedure Generic_Print(Object : Vector.Vector) is
Begin
  for Item of Object loop
      Print( Image(Item) );
   End loop;
End Generic_Print;

As it stands Generic_Print is not a procedure, but the thing from which those procedures are made; however. because Ada allows shared-generics, said functionality could be defined as part of an OS’s library system: there you would be able to use it just as instantiation allows you to use it in Ada as a “given X parameters, do Y thing.” function returning a subprogram.

Hell, if the abstractions on data-type and interfaces were in-play, you could re-use that entire subsystem plus generics to make the instantiation-engine for said library-system.

Yep, that is.

Shared implementation is indicative. The compiler can do that (*), but you cannot.

(*) I am not sure if it still true with all new language additions. So far GNAT cannot even guarantee that little as when generic body compiles then no instantiation would fail inside the body. Such a perfect reuse! :rofl:

One of the most important parts of re-use is not Dry (do not repeat yourself) but readability. I think Generics wins there compared to abstract tagged types, inheritance and interfaces.

Are you joking?

  • Readability is a part of re-use only when you do cut-and-paste. Which is pretty close to the nature of macro expansions.
  • Generics when used at production scale are totally unreadable. Error messages that nobody can understand. Most of GNAT bugs were related to generics. GPS fails to navigate generic code. Debugger does not work anyway. And Generics are fundamentally non-testable.

If the leading compiler vendor cannot understand the mess what can be said about us, lame programmers…

I do not want to offend anybody, but it seems that nobody who argued for generics ever used them massively. I did. I can present a lot of very complicated generic code. Even more code I cannot show, because it is proprietary. That thing is especially nasty as I tried to use generics to enforce static type checks. It was a success, but it led to a monstrous code, which GNAT could not compile for a quite long time. Even longer it could compile, but ran out of memory. I needed 2GB per compiler task. It was fun to tune -g switch settings for half a dozen targets, we had. No need to say that it could not compile natively on an ARM 32-bit. It still cannot. We have to buy a cross compiler from AdaCore to work it around.

Since then I am actively preaching against generics.

Eh, but GNAT is not Ada.
Neither is AdaCore Ada.

That you are so quick to point to GNAT is indicative that we-the-community need another free/open implementation.

I generally agree; generics are VERY good (and arguably under-used).
The static polymorphism and ability to decompose some subsystem in particular.

Not necessarily; readability also comes from good structuring.
As an example, in this implementation of type-conversions for a toy language/VM Generics and overloading are used to reduce the MxM combinatorial explosion to a linear one.

That’s pretty odd — I don’t think I’ve ever had an unreadable error-message dealing with generics, though perhaps I simply don’t remember.
But, again, a lot of this looks like GNAT-isms.

That’s fine, interesting even.
I’m wondering, would it be possible to get some good [particularly bad] examples, say as part of a test-suite?

Several things to note:

  1. GNAT is not great with static-checks, certain defaults for the compiler indicate this mindset.
  2. Your example indicates some languishing in GNAT’s generic implementation.
  3. Some SPARK limitations indicate an unwillingness to invest much into generics; particularly that there is no SPARK verification on the generic-proper, even though doing so would allow for much faster proof WRT code that uses generics and if you could prove a generic itself then, just like Ada’s generic-instantiation, you could be assured that all [valid] instantiations are correct.

This sounds very much like a product of GNAT’s implementation.

You can see a medium-complex generic code with lots of generic inheritance here:

Differently to tagged types there are at least three ways of inheritance with generics:

  • Instantiation inside the package specification
  • Formal generic package
  • Generic child

All of them have their disadvantages. E.g. instantiation requires to be a tree and does not provide library level instance package. You immediately run into diamond inheritance problem if the graph is a DAG, which is always the case.

The packages mesh starts with usual formal base types like number, index etc and then rapidly escalates into a total mess as it must produce a dozen of interconnected pckages like intervals, numbers, sets, dimensioned intervals etc.

The idea was to have a generic universe of packages for each of 20 or so datatypes. So elementary operations like Get. Set, Send, Request were statically typed. There were jailbreak primitive operations on top to break out of generics when you need to log, trace, network etc. But the result was that each middleware driver must instantiate a huge number of generics for each data type it supported. While the tagged core was very simple. Inherit from, say, a full-duplex driver, override a dozen primitive operations, you are done. The contrast is staggering.

It is a real-time middleware, lots and lots of tasking, protected objects, loadable plugins, so no SPARK,

I doubt any other vendor could do better.

1 Like