How to inherit string handling?

Assume you define a “new” fixed string type:

type s_type is new String;

s_type inherits primitive operations from the type sting.
However, is it any simple way where s_type also “inherits” operations from Ada.Strings.Fixed ?

reinert

Since the operations are in a different package from where String is declared (Standard), I don’t think the operations in Fixed count as primitive operations for String types. I think in order to inherit an operation, it has to be a primitive operation.

Yes, but is there a possible (almost-)workaround?

Do you mean, a simple way other than subtype S_Type is String; ?

The idea was to be able to do something like this:

declare
S : S_type := “This is a test”;
begin
S.New_Operation; – with this “OOP notation”
end;

(and at the same time easily/directly inherit the standard (library) operations for String).

Just experimenting with a string/command parser for my program:-)

You could try turning on GNAT extensions if using GNAT. They have Object.Operation notation for all types (which isn’t a language feature yet), but I don’t know how it works with operations declared in external packages.

Otherwise you will probably have to do some work and make your own tagged type to wrap around unbounded_string and then put in wrapper operations for all of the functions/procedures you want. Something like:

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Maps;      use Ada.Strings;

package OO_String is

   type Tagged_String is tagged record
      Implementation : Unbounded_String;
   end record;

   -- Quick conversion functions
   function "+"(Source : String) return Tagged_String is
      (Implementation => To_Unbounded_String(Source));
   function "+"(Source : Tagged_String ) return String is
      (To_String(Source.Implementation););

   function Index (Source   : in Tagged_String;
                   Pattern  : in String;
                   Going    : in Direction := Forward;
                   Mapping  : in Maps.Character_Mapping
                                := Maps.Identity)
                   return Natural
   is (Index(Source.Implementation, Pattern, Going, Mapping));

end OO_String;

Then you can do things like:

   My_String : Tagged_String;
   Index : Natural;
begin
   Index := My_String.Index(".");

Unless this is a way of learning, consider re-using one of the numerous libraries out there that attempt to wrap all those strings operations. They have been mentioned several times in this forum, and their various authors are in general participating here.

(at the time, I contributed GNATCOLL.Strings as such as an attempt – the AdaCore people seem to have now come up with a different package in VSS, I think there is also something similar in pragmARC, and maybe Vadim Godunko also has another version). Seems like we have all had a slightly different goal in mind, and different approach to the code, so take the time to examine the packages to see which one would better match your needs.

2 Likes

For what it is worth, the ARG approved general “object.operation” notation for inclusion in the next Ada standard.

There is a directory full of “Approved” AIs that relate to enhancements and/or corrections to Ada 2022,

1 Like

This is a massive usability improvement! Many thanks to the ARG!

I’m excited honestly. I was following the discussion on this pre Ada2022 and it didn’t look like it was gonna get through.

1 Like

I disagree.
I think it’s a bad idea, and I’d honestly rather remove object.operation altogether, going back to Ada95-style than have it for all types.

It significantly would improve discoverability of subprograms with LSPs.

My worst example is Ada.Strings.Unbounded.Length (A_Str), even with a package rename as a shortener, the prefixes get in in the way with a lot of repeated operations on the same type. A_Str.Length would be much preferred.

Is there a better way currently to deal with it?

You can do a local subprogram rename, but that comes with it’s own problems if that becomes ambiguous. But it would give you Length(A_Str) vs A_Str.Length. I don’t know if that is better than what you are already doing or not though.

I like the object.operation notation a lot better because it identifies the operation as a primitive operation of the type of the object. Now I know exactly where to start looking for the operation code (the package the type is declared in). I don’t have to search through various random packages that have been made use visible to figure out what code I am dealing with. I don’t always have access to IDEs, text editors, tools or even internet on some systems.

Each to his own! You definitely don’t have to use it … :wink:

1 Like

There’s lots of ways to handle this, use and use [all] type are particularly useful for this.

It really depends on what you’re doing, and the purposes you have are.
If you’re designing a new type, deriving Unbounded_String, then private with and use in the private section are probably the best bet.

If your data-type merely uses those operations a lot (rather than having to define them) then with in the body, and use use locally in the subprograms (if there’s a few instances, if the use is widespread use at the implementation’s package level.

One hybrid way to do things, and this works with SPARK when you need operations that you can’t prove on, is to have a sub-package nested in the package where you can use use (and turn off SPARK) for implementing all the operations you’ll be using, and then renames-as-body for the implementation.

Most of the demand for object.action notation seems to come from a bent towards catering to making writing easy, in the C/C++ sense, rather than the reading.

(Another, though tangential irk, is that there are programmer that essentially think that object.action notation is OOP. I had a big discussion with someone a while back, who refused to learn, who simply could not understand that package and OOP-class are two very different things — I suspect that extending object.action notation will do more harm than good WRT to readability, maintainability, and [frankly] teachability for Ada.)

For me I think it will do the opposite, it’ll help readability and maintainability. I can’t speak on teaching though. It would be more recognizable to new folks but I don’t know if that leads to better learning or not.

But I can understand how it can be seen differently by different people. Readability and maintainability are definitely very subjective lines to set. So it may be worse for some and better for others.

The dot notation is obviously handy, especially because it does not require direct visibility. On the other hand it is plainly inconsistent with multiple arguments of the operation.

(As always) I would prefer a proper typed (you can say OO) solution: an abstract record interface. If you want anything to be looking like a member of T, which X.Foo or X.Baz (…) is. Then, just say so by inheriting from the interface rather than by adding language hacks.

It could be done automatically in clear cases. It should be done explicitly in others if the programmer wants it.

You might be interested in this; it proposes two such abstractions/enhancements:

  1. The equivalent of CS’s Abstract Data Type; intended to “hang” all your SPARK proofs on, as well as “turning generic formal parameters inside out”.
  2. An abstraction of type/interface, the “shape” of the type; the purpose of this is to be able to (1) specify the usage and class of a type and its properties [e.g. attributes]; and (2) define Ada’s type-system within the type-system, making the “shape” of types something describable in Ada.

Well, it states the obvious. Hacks are hacks. They hit you back when it comes to the code of real-life scale.

However, I am afraid that the proposal still lacks being inheritance proper. Without inheritance, the only is no solution. You must be able to inherit from an abstract array and pass it where array is expected. No helper types, not aspects, no other mess, that should work out of the box. Attribute is not a property, attribute is a plain primitive operation called in the form X’A (…) etc. If you have that you would not need to “describe” anything but class memberships.

And surely it would simplify SPARK as you would inherit predicates proven to be true on the interface you inherit from.

My own two cents for dot notation is that it allows more clarity when using a functional style, as you can chain function calls on the returned type. The alternative is overdose of parentheses a la LISP, or having more explicit values that could be mere temporaries.

The thing has its own wikipedia page: Uniform Function Call Syntax - Wikipedia

1 Like