"Subtype Predicates, or Why Was I Not Imformed?", a Drama in Two Acts

The abridged TL;DR version is at the bottom.

Act I : Vjalmr Has Issues with Types. Surprise!

For learning Ada I have set out to complete all the Advent of Code challenges, starting from 2015. I skipped day 4, because I am still not comfortable with bit fiddling for an MD5 implementation, so I ended up on day 5.

Here I need to compare some characters, so I thought “Hey! I will just make a Vowel type, and compare the characters that way!”, forgetting all about what I had read here: Wikibook - Characters as Enumeration Literals

This literal ‘A’ has nothing in common with the literal ‘A’ of the predefined type Character (or Wide_Character).

After a lot of digging, which seems to be the norm with this language, I found a blog by one Jim Rogers, who introduced me to subtypes with Static_Predicate. Just to make sure Mr. Rogers wasn’t pulling my leg, I looked it up in the reference manual, and it was indeed there 3.2.4 Subtype Predicates.

Now, I could create a subtype of Character with only vowels, and compare with my string.

Interlude

Is this the right way of doing it? I know it works, but I am not sure.

Could I cast the character to my type? But if I do that, then all the characters that are not of that type would cause an exception of sorts, if I understand correctly.

Please send your insights, as currently my code looks like the following:

--  Subtype and variable definition, with initialization
subtype Vowels is Character with
     Static_Predicate => Vowels in 'a'|'e'|'i'|'o'|'u';

Test_String : String := "I have some vowels in me.";
Vowel_Count : Natural := 0;
--  How many are there?
for C of Test_String loop
   if C in Vowels then
      Vowel_Count := Vowel_Count + 1;
   end if;
end loop;

Act 2: Vjalmr Would Like to Contribute

As enlightening the journey was, finding this information was not as easy as I had hoped. They are actually on the Learn Ada website, but under “Design by contracts”, and also I found it under “Ada for C++ or Java Developer”.

I had not gotten to contracts yet, and I am neither a C++ or Java developer, so I didn’t know to look there. I wanted a type.

Now, the prerequisite for this question is: Am I going about doing it the right way?

If I am, then I would like to add this tidbit to either the WikiBook, or to the ada-lang.io tutorial, which is where I actually got started in the first place - even though there are only two entries.

How would I go about doing this?

Fin

TL;DR

Should I be using Subtype_Predicate or a unique type for character comparison if I want to see if characters of a string are vowels or not?

If I should use this, could I somehow contribute to the tutorials, or the WikiBook so that this information is easier to find, so the next person who wants to do this doesn’t spend an hour figuring it out?

I don’t know the answer to your question. I’m not sure Static_Predicate counts as a contract in this circumstance. Have you thought about using a Character_Mapping?

1 Like

You are probably right and that’s why it is confusing me.

I have not, but I will also give that a try - just to see how it differs. Thank you.

This also occurs to me: I haven’t looked at the problem yet, but you could define an array over a range of Character. That’s probably the simplest way, and I’m pretty sure it’s both idiomatic and efficient.

with Ada.Text_IO;

procedure Test_Vowel is

   package IO renames Ada.Text_IO;

   subtype Alphabet is Character range 'a' .. 'z';
   Is_Vowel : array (Alphabet) of Boolean
      := ( 'a' | 'e' | 'i' | 'o' | 'u' => True, others => False );

begin

   IO.Put_Line (Is_Vowel ('e')'Image);   --  TRUE
   IO.Put_Line (Is_Vowel ('f')'Image);   --  FALSE

end Test_Vowel;

What you did makes sense based on examples that I’ve seen

Where they helpful and did you want more? I stopped all a sudden because of a bunch of personal things.

So I did this, and it looks like you would expect as the following:

with Ada.Strings.Maps; use Ada.Strings.Maps;
...
Vowels : constant Character_Set := To_Set ("aeiou");

The only interesting bit is, I cannot do an in loop on them, I have to use Is_In():

if Is_In(Current_Character, Vowels) then
   Vowel_Count := Vowel_Count + 1;
end if;

But it works just fine. One of the most eye opening aspects of this exercise is that there are more than one way of doing things with Ada. For some reason I had expected it to be “The Ada way, or the Highway” due to the stringent nature of the language, but that seems not to be the case.

They were helpful indeed, and of course more would be appreciated - not just by me, but by others as well. Some things were not too clear, such as Subprograms actually being the building blocks of the program i.e. procedures and functions.

This was confusing coming with some Pascal knowledge - making me think a “subprogram” was like a unit.

I would love to help out once I actually know what I am doing - but also just for editing, structuring, and commenting, if this would be of any help to the community.

I’m not sure from your post if what you did worked for you or not.

I created this from your examples:

with Ada.Text_IO;
procedure Vjalmr is
   subtype Vowels is Character with
      Static_Predicate => Vowels in 'a' | 'e' | 'i' | 'o' | 'u';

   Test_String : String := "I have some vowels in me.";
   Vowel_Count : Natural := 0;
begin
   for C of Test_String loop
      if C in Vowels then
         Vowel_Count := Vowel_Count + 1;
      end if;
   end loop;
   Ada.Text_IO.Put_Line (Item => Vowel_Count'Image);
end Vjalmr;

and tried it

$ gnatmake -gnata vjalmr.adb 
x86_64-linux-gnu-gcc-12 -c -gnata vjalmr.adb
x86_64-linux-gnu-gnatbind-12 -x vjalmr.ali
x86_64-linux-gnu-gnatlink-12 vjalmr.ali
$ ./vjalmr 
 8

which is the right answer (your definition of Vowels does not include 'I').

With GNAT, assertion checking, which includes pre- and post-conditions and predicates, is not turned on by default, which is why I always use -gnata.

What you did worked, but as pointed out, there are other ways to achieve the same thing. At the basic level, what you want is a set of Character and a membership test against that set. You can do that directly in some languages, but in Ada it takes a generic instantiation for the general case. There are usually multiple ways to do things in Ada. The mapping from Character to Boolean presented by cantanima is a common way to implement a set when the universe is discrete, and generics such as PragmARC.Data_Structures.Sets.Discrete create them for you. Character_Set is a custom implementation of a set for Character.

The only way that lets you use [not] in is a subtype, but since Ada 12 one can use a disjoint set for a membership subtype as in your predicate, so you could just do

if C in 'a' | 'e' | 'i' | 'o' | 'u' then
1 Like

Yes, what I did worked, with several different approaches to solve the same problem.

Not part of the spec, correct.

Which is what I have come to realize. Thank you for your in depth answer.

These interesting bits of information, they are there I am sure, but it takes a lot of digging, trial and error to figure them out.

I have downloaded, and gone through a lot of but not all of course, of the PDFs available on learn.adacore.com and they do help. Generally the information I need for what I am doing is further along than where I have read to. I think sometimes my issue is that I attempt to do things the “non-Ada way” - due to using other languages.

The only interesting bit is, I cannot do an in loop on them, I have to use Is_In():

Maybe I misunderstand you, but with Ada 2022 you can do this (building on the example I have above):

pragma Ada_2022;

--  much of the above example program is the same, but the end:
begin

   for V in Alphabet when Is_Vowel (V) loop
      IO.Put (V);
      IO.Put (' ');
      IO.Put_Line (Is_Vowel (V)'Image);
   end loop;

end Test_Vowel;

This is very clean, I like it.

I am however using 2012 at the moment, based on advice I read somewhere that compilers don’t fully support 2022 yet?

This works in GCC back to version 11 (you have to say pragma 2020 for that older compiler!)

Other 2022 features will have made their appearance in successive releases (with the occasional issue on the way).

1 Like

Thank you, Simon!

The more you know.