“Preserve the conventional meaning of overloaded operators” — Ada Quality & Style, Overloaded Operators

Mmmmm, bite me.

It started simple, just repurposing the unary plus as a concise, user-defined function name. I mean, it’s not like it actually does anything to a value, y’know? If you look in the code of the various projects I work on, you’ll often see this:

function "+"(S: String) return Unbounded_String renames To_Unbounded_String;

From there the next step along the road to operator abuse was very small, a barely noticeable increment–just redefining unary minus. I mean, what’s the harm? “+” has been redefined, how could it hurt to redefine “-“, especially since its redefinition is the inverse of “+”? That’s consistent, and conventional, right?

function "-"(U : Unbounded_String) return String renames To_String;

For quite a long time that’s all the further I was willing to go. Oh, I might occasionally redefine unary plus to do some other kind of value conversion, but that was just a variation on a theme.

The Path Darkens

And then I started working with XML.

Now there’s tons of libraries available for dealing with XML, and AdaCore provides XML/Ada, which was sufficient for my XML needs in Ada projects. But, y’know, sometimes all you need to do is write some simple XML, nothing fancy, like for reports or data summaries, and not anything you’re going to read in, so there’s really no need for a full-up heavyweight XML service.

All you need sometimes is to be able to do some easy XML output, which led to the development of XML_EZ_Out. Deciding on the services for a simple XML output library isn’t really that difficult. You got the header stuff, and outputting elements, and content, and handling the element nesting and attributes. It’s all pretty straightforward to come up with some variations for outputting XML elements, with or without attributes, without or without content, etc.

But then I had an idea.

Here’s a really simple XML element:

<player lastName=”Carew” firstName=”Rod” team=”Twins”/>

Wouldn’t it be great if the code one writes to output that element could resemble the generated element to some extent? Something like:

Output_Tag(F, "player", ("lastName" = "Carew", "firstName" = "Rod", "team" = "Twins"));

That would be sooooo neat, right?

And thus was the “=” function abusively overloaded. I jettisoned any sense of conventional meaning for this operator overloading. Not only had I abandoned the unary operator constraints, I left the relational meaning of “=” on the scrap heap as well. There were no Boolean comparisons being done here, this was blatant object creation for inclusion in array aggregates. I partook of numerous variations of the value (RHS) of the “=”, for String, Unbounded_String, Integer, Float, it…was…glorious.

And I couldn’t stop there.

Losing Myself

One of the little tricks I like about Python is being able to create these impromptu arrays and index into them as part of an expression. Like this:

print ("this here", "that there")[which]

Where the variable ‘which’ typically contains a Boolean value. If which is False, “this here” is the result, if True, “that there” is the result. This is nice and concise, and can be done up on the fly in the code.

I thought that something like this would be nice to have in Ada, since this crops up now and then and I’ve ended up creating little functions or map containers to provide this kind of service. (And it’s not just two values with a Boolean selector, sometimes there’s a small set of strings that one wants to select from.)

So what I’d really like is being able to do something close to:

Put_Line(("this here", "that there")(Which));

That simply can’t be done in any general way in Ada, since you can’t define an array type in which you can directly specify various-length string literals as aggregate values.

This first thing you’re going to need for something like this is an unconstrained array of some type. That will allow for varying numbers of choices to select from. And since strings of various length are going to be supplied, that means a varying length string type, so we’ll just go with Unbounded_String.

type Impromptu_Strings is array (Natural range <>) of Unbounded_String;

Now how do you create an an instance of the array containing the desired strings? The first thought is the old familiar “+” renaming of To_Unbounded_String. That would work, but later on we have to be able to select a specific value from the list, so some operational consistency would be a good thing. Plus, you have to mark every string, and while you’d get a compilation failure if some were missed, reducing opportunities for error is a good goal.

Let’s think out of the box, maybe take some inspiration from XML_EZ_Out. Could a binary operator somehow play here?

Why yes, yes it could:

function "or" (Val1 : String; Val2 : String) return Boolean_Impromptu_String; function "or" (Set : Impromptu_Strings; Val : String) return Impromptu_Strings;

Wait a minute, what’s the deal with “Boolean_Impromptu_String”? Well, if the intent is to pick using a Boolean index, then it doesn’t make any sense to allow an array with more than two values, so Boolean_Impromptu_String is simply a two-element subtype:

subtype Boolean_Impromptu_String is Impromptu_Strings(0 .. 1);

It’s still an Impromptu_Strings instance, but allows for length verification when using a Boolean index, as we’ll soon see.

Okay, so we’re using “or” to build up the list of values, which is not totally crazy since the intent is to choose this value or that value or some other value, etc. What about specifying the index, Boolean or numeric, that is going to be used to get a value? Let’s think about it, what would be a natural complement to “or”? It would have to be a binary operator and suggest selection in some way and be able to return one of the original values from the list. I think the “and” operator might just fit the bill here:

function "and" (Set : Boolean_Impromptu_String; Choice : Boolean) return String; function "and" (Set : Impromptu_Strings; Choice : Natural) return String;

“and” is used in set processing to specify set intersection, and that’s a quite plausible way of looking at how it will be used to select out one of the values in the list.

With these two selection functions, and a use clause for the Impromptu_Selection package that provides them, we can write code like this:

procedure Impsel is Which : Boolean := True; Letter_Choice : Natural := 2; begin Put_Line(("This here" or "That there") and Which); -- "That there" Put_Line(("A" or "B" or "C") and Letter_Choice); -- "C" (0-based indexing) Put_Line(("A" or "B" or "C") and False); -- Constraint_Error! end Impsel;

Ada 2012 will be providing direct language support for this kind of capability via conditional and case expressions. That I think would look something like this (but I’m not 100% positive since I’m waiting for a full-featured Ada 2012 compiler):

Put_Line(if Which then "This here" else "That there");

and

Put_Line(case Letter_Choice is when 0 => "A", when 1 => "B", when 2 => "C");

It’s nice to have that built into the language, but it does seem a bit of a heavy syntax to use for something like this. There’s also been some grumbling due to the inclusion of what has been perceived as statements now being part of expressions. The Impromptu_Selections approach, in contrast, leaves the selections still looking like expressions.

Following is the package spec, body, and a brief test program for Impromptu_Selections. It’s ripe for additions, including being able to build up selection lists of other types, so go crazy with it. Don’t worry about me, I’ve got this whole Operator Overloading thing totally under control, I do not have a problem.

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; package Impromptu_Selection is type Impromptu_Strings is array (Natural range <>) of Unbounded_String; subtype Boolean_Impromptu_String is Impromptu_Strings(0 .. 1); function "or" (Val1 : String; Val2 : String) return Boolean_Impromptu_String; function "or" (Set : Impromptu_Strings; Val : String) return Impromptu_Strings; function "and" (Set : Boolean_Impromptu_String; Choice : Boolean) return String; function "and" (Set : Impromptu_Strings; Choice : Natural) return String; end Impromptu_Selection; package body Impromptu_Selection is function "+"(S : String) return Unbounded_String renames To_Unbounded_String; function "-"(U : Unbounded_String) return String renames To_String; ---------- -- "or" -- ---------- function "or" (Val1 : String; Val2 : String) return Boolean_Impromptu_String is begin return (+Val1, +Val2); end "or"; ---------- -- "or" -- ---------- function "or" (Set : Impromptu_Strings; Val : String) return Impromptu_Strings is begin return Set & (0 => +Val); end "or"; ----------- -- "and" -- ----------- function "and" (Set : Boolean_Impromptu_String; Choice : Boolean) return String is begin return -Set(Boolean'Pos(Choice)); end "and"; ----------- -- "and" -- ----------- function "and" (Set : Impromptu_Strings; Choice : Natural) return String is begin return -Set(Choice); end "and"; end Impromptu_Selection; with Impromptu_Selection; use Impromptu_Selection; with Text_IO; use Text_IO; procedure Impsel is Which : Boolean := True; Letter_Choice : Natural := 2; begin Put_Line(("This here" or "That there") and Which); Put_Line(("A" or "B" or "C") and Letter_Choice); Put_Line(("A" or "B" or "C") and False); end Impsel;