I’m learning Ada. I find some of the package names, like Ada.Strings.Unbounded, are too much typing (as in typewriter typing, not what types objects are), but I prefer not to use the use statement because then I can’t remember which procedures and functions came from where. So I came up with a way to abbreviate Ada.Strings.Unbounded as Asu, and Ada.Text_IO as Atio.
My question is whether what I’m doing is the best way to abbreviate long package names. My code follows:
steves_abbrevs.ads follows:
with Ada.Text_IO;
with Ada.Strings.Unbounded;
package Steves_Abbrevs is
package Atio renames Ada.Text_IO;
package Asu renames Ada.Strings.Unbounded;
end Steves_Abbrevs;
Main program abbrev_example code follows:
with Steves_Abbrevs;
use Steves_Abbrevs;
procedure Abbrev_Example is
Out_String: Asu.Unbounded_String;
begin
Out_String := Asu.To_Unbounded_String("My abbrev example!.");
Atio.Put_Line(Asu.To_String(Out_String));
end Abbrev_Example;
In short, “whatever rocks your boat”. Literature shows all kinds of practices, some hate the use clause, others don’t care. Renaming package was common. So you’re good to go.
With
Ada.Strings.Unbounded_Strings,
Ada.Characters.Handling;
Package Example is
Package ACH renames Ada.Characters.Handling;
Package US renames Ada.Strings.Unbounded_Strings;
--…
End Example;
I usually use a use clause inside deeper scopes if I want to avoid qualifying with full package names. For someone reviewing the code, the local use of the use clause hints where some things may originate from.
procedure Local_Use_Example is
Use Ada.Strings.Unbounded;
Use Ada.Text_IO;
Out_String: Unbounded_String;
begin
Out_String := To_Unbounded_String("My example!.");
Put_Line(To_String(Out_String));
end Abbrev_Example;
For me I have different considerations:
If it is a package that is universally useful across the whole of my current unit, is a well known package, and is unlikely to have ambiguous issues (like Ada.Text_IO), then I might just use it for the file. However, I try to keep those to a minimum.
Outside of that I generally either use package renames (though I do them locally instead of a separate file) or I will put the use more locally (like in a specific procedure):
procedure Do_Something is
use Some_Package;
begin
-- stuff
end Do_Something;
Also note that if you do a global renaming like that you don’t need an enclosing package if you are ok with multiple files. You can do them individually if you like (this is not my preference):
atio.ads
with Ada.Text_IO;
package Atio renames Ada.Text_IO;
My opinion is that my fingers aren’t tired. I don’t need use or renames in general. I do rename e.g. a device name as it is used many times and the package is e.g. the EXTI package so there is no ambiguity. So I use renames and use only in rare cases.
I know that AdaCores learn site would benefit from dropping use.
My eyes are old and unfortunately they don’t work correctly, so in my case I have to balance readability. I prefer the spelled out sentence like syntax of Ada, but sometimes too much is just not practical for me to see correctly. I find renames of longer items sometimes useful for readability. I like to keep them as local as makes sense though.
The other thing I have found is in some cases, renames really help portability later. Being able to change a single rename clause to change up implementations is really useful in the things I do.
What do you do when you don’t need all of the packages in your abbreviation package? When you want a renaming of a long-named package not in your abbreviation package? Certainly there are times when a shorter name is useful, but this doesn’t seem like a good approach in general.
JC001, my plan is to put my most commonly used abbreviations in one .ads file. For the others, I can either use separate .ads files for each seldom used abbreviation, or use a use command within a procedure/function, or just spell the whole thing out. Thanks!
I avoid use Package.Name; since it makes it difficult to know where subprograms, types and enums are brought in from, though you can confine these to a particular subprogram.
Excessive verboseness of your own package names can be an indication of improper package structure. I’ve noticed that it sometimes indicates a sibling package should really be the parent of the current unit.
On the issue of applying the use (apologies for slightly hijacking this thread) being conservative makes a lot of sense but sometimes it can force some things to be a bit awkward. For example, given the following:
package My_Enums is
type My_Enum is (My_Enum_1, My_Enum_2, My_Enum_3);
end My_Enums;
and a simple client:
with My_Enums;
procedure My_Enums_Client is
E1 : constant My_Enums.My_Enum := My_Enums.My_Enum_2;
E2 : constant My_Enums.My_Enum := My_Enums.My_Enum_1;
Impossible : exception;
begin
if E1 = E2 then
raise Impossible;
end if;
end My_Enums_Client;
then that fails to compile because the = isn’t visible without use-ing the package. One way to fix that if use is to be avoided alltogether is to roll-out the manual version i.e. :
...........................
if My_Enums."="(E1, E2) then
raise Impossible;
end if;
...........................
which arguably isn’t massively pretty.
I don’t suppose there’s a workaround for that short of use-ing the package in the declarative part, is there? (like a compiler flag which drops the use requirement for intrinsic-like subprograms?)
By the way, with the above second version of = there are no compiler warnings, however if I use the package (and then simply do if E1 = E2 then) then I’m getting a Condition is always false warning which is great that it picks that up but it’s interesting how it’s not picking up the first version? (project is setup with gnat_native=14.2.1 & gprbuild=22.0.1 in Alire)
You don’t have to use the package you can import all operators for only a specific type:
with My_Enums;
procedure My_Enums_Client is
E1 : constant My_Enums.My_Enum := My_Enums.My_Enum_2;
E2 : constant My_Enums.My_Enum := My_Enums.My_Enum_1;
Impossible : exception;
use type My_Enums.My_Enum; -- brings in all operators
begin
if E1 = E2 then
raise Impossible;
end if;
end My_Enums_Client;
If you want all primitive operations for the type, then you upgrade to
use all type My_Enums.My_Enum; -- brings in all primitive operations
The rest of the package is not use visible doing it this way. Also constraining it to the operation in question keeps it localized to just that subprogram and not the rest of your package if it is also in a package.
Use statements are very useful for several conditions:
In the Spec, global, when the package itself is intrinsically tied to the types being imported in even the interfacing.
In the Body, global, when the operations being used are intrinsically tied to the operations being imported; (e.g. Use Ada.Strings.Fixed for a string-manipulation package.)
In the body of a subprogram, locally, or task when many types or operations are used, or when usage is very frequent;
In a Declare-block for cleaning up code aestetically. (In this sense, it’s kinda/sorta like Pascal’s with-block, though on the namespace rather than some named structure.)
Thanks @cantanima and @jere for that, I remember reading about use (all) type but totally forgot it existed!
Applying that in the declarative section of a subprogram sounds like a good compromise between full and localised visibility (especially if a subprogram makes heavy use of a relatively small number of packages).
Ada 95 Problem Solving and program design; Feldman & Koffman
“The Use clause: This is introduced in Chapter 8. Ada industry practice generally avoids the use clause for a number of good reasons. We avoid it here, in general, because qualifying all references to package resources helps the student to really understand which resources are provided by which libraries.”