Advice on operator overloading

I’m developing a thick binding for glpk, the Gnu Linear Programming Toolkit. I’ve worked with (and on) the Sage computer algebra system before, and something it allows, which I’ve always liked, is the ability to add constraints to a linear program using ordinary mathematical notation; e.g.,

lp = MixedIntegerLinearProgram()
x, y = lp[0], lp[1]
lp.add_constraint(x + 1 <= 2)

While reading the RM, I realized that the same is possible in Ada, so I implemented it; for example, the following compiles and runs correctly:

declare
   Program : Linear_Program (Num_Vars => 3, Num_Constraints => 3);
begin
   X : constant Expression := Program (1);
   Y : constant Expression := Program (2);
   Z : constant Expression := Program (3);
   Program.Set_Constraint (1, X + Y + Z <= 100.0);
   Program.Set_Constraint (2, 10.0 * X + 4.0 * Y + 5.0 * Z <= 600.0);
   Program.Set_Constraint (3, 2.0 * X + 2.0 * Y + 6.0 * Z <= 300.0);
end;

(I plan to remove Num_Vars and Num_Constraints as discriminants, and switch from Set_Constraint to Add_Constraint, given that someone may want to warm boot a solution after adding constraints.)

Then I recalled that the recent Advent of Code used a linear program whose constraints were actually equality. So I could, in principle, specify constraints as follows:

   Program.Set_Constraint (1, X + Y + Z = 10.0);

…and I implemented that, too.

All in all, I entirely too pleased with myself. :grin: But the use of = somehow triggered memories from when I first studied operator overloading, how many criticized its overuse, often in unintuitive ways.

I don’t recall reading those criticisms lately, and this does not strike me as unintuitive, but I was curious how regular Ada programmers feel about operator overloading:

  • When is it a good idea to use?
  • Do you think there’s something about Ada’s approach to it that makes it safer, or less prone to abuse, than other languages’?
  • Does this seem like a legitimate case?

Thanks in advice for any advice.

Perhaps the “funnest” operator overloading I did was on -, though it could have been =.
Something to the effect of:

Generic
  Type Real is is digits <>;
  Delta : Real;
Function Check( X, Y : Real ) return Boolean;
Function Check( X, Y : Real ) return Boolean is
Begin
  Return abs (X - Y) <= Delta;
End Check;

Function Check_Instance is new Check( Float, 0.5 );

Function "-"(Left, Right : Float) return Boolean
   renames Check_Instance;

Essentially so you could if X-Y then.
Not really intuitive, but when you’re doing a lot of “if X is approximately Y…” style statements it can clean things up a bit.

1 Like

:astonished_face:
That’s appallingly close to C / C++…

…which I take as an implicit endorsement of what I’ve implemented.
:grin:

edit: Those could be interpreted as fighting words, which I don’t mean, so let me both clarify and walk that back a little. Using if X = Y then with that overload actually makes a lot of sense. It’s the if X - Y then bit that got me, and reminded me (however wrongly) of the habit in C / C++ of doing things like if (int i = ...) { ... }; i.e., assigning inside the condition of an if.

The more I think about it, the less similar to C / C++ it seems, so I’m deleting this in a few minutes.

Don’t delete it, it’s an interesting reaction.
Besides, the usage of a Boolean returning - is really the crux: you can use operators to good effect to clean up readability in code… but it’s double-edged, if you overload Unchecked_Conversion as +, you might have a line like X : Target_Type renames (+(+(+Y)));

2 Likes

Defining an abstract data type with a set of operations is more than OK.

2 Likes

I’m new to Ada, but i can tell you i loathed the idea of << and >> as “output” and “input” operators in C++. I don’t mind the notation, and even + for concatenation i think is good, but the problem comes from when people suddenly loose track of what it’s really for or it goes library wide.

You gotta ask yourself if it’s convenient, intuitive, and not conflicting. See, with + we know strings don’t actually get added together, but with << and >>, firstly instead of cin >> () and cout << () we could’ve easily had io.in() and io.out() and io.err(). They did << and >> for the sake of doing it, IMO, and they used operators that most people aren’t going to touch. That was abuse.

And before anyone says it was so they could use std::endl, why not just teach escape sequences? It’s not like C++ doesn’t have them and it’s perhaps better to at least warn people that “\” actually does something. What? Someone might do it later on with file io and wonder why notepad et al don’t show things on a new line? Perfect, you can then branch into the difficult conversation of how not everything follows the same standards and that’s why we need them.

1 Like

That’s helpful. It’s also what I had in mind myself.

That’s actually the one in my head. I remember the use of + for concatenation as being controversial, though maybe that was just Java (?) advocates coming up with reasons their language didn’t allow that. That makes sense, to me, too, though Ada uses &, which I get, but to me it makes less sense that +.

Oddly, that always made sense to me, and I never heard it was controversial. But I see what you’re saying. :+1:

This is an intuitive use within its domain.

Overloading, especially in mathematical applications, is an established practice.
Take a look at this code from sets_generic.ads by Michael Feldman (gwu.edu/~mfeldman/cs1book/software.html):

GENERIC

  TYPE Universe IS (<>);

PACKAGE Sets_Generic IS

------------------------------------------------------------------
--| Generic specification for sets over discrete universes
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1998
------------------------------------------------------------------

  TYPE Set IS PRIVATE;
  Phi: CONSTANT Set; -- empty set

  -- constructors

  FUNCTION "+" (S: Set; E: Universe) RETURN Set;
  FUNCTION "-" (S: Set; E: Universe) RETURN Set;
  -- Pre:  S and E are defined
  -- Post: returns S with E inserted or deleted respectively;
  --   "+" has no effect if IsIn(S,E); "-" has none if NOT IsIn(S,E)

  FUNCTION Singleton(E: Universe) RETURN Set;
  FUNCTION "+" (E1, E2: Universe) RETURN Set;
  -- Pre:  E, E1, and E2 are defined
  -- Post: returns a set made from one or two elements

  FUNCTION "+" (S, T : Set) RETURN Set;
  FUNCTION "*" (S, T : Set) RETURN Set;
  FUNCTION "-" (S, T : Set) RETURN Set;
  -- Pre:  S and T are defined
  -- Post: returns the union, intersection, and difference of
  --   S and T, respectively

  FUNCTION "-"  (S : Set) RETURN Set;
  -- Pre:  S is defined
  -- Post: returns the complement of S

  -- selectors
  FUNCTION IsIn (S : Set; E : Universe) RETURN Boolean;
  -- Pre:  S and E are defined
  -- Post: returns True if and only if E is a member of S

  FUNCTION IsEmpty (S : Set) RETURN Boolean;
  -- Pre:  S is defined
  -- Post: returns True if and only if S is empty

  FUNCTION SizeOf (S : Set) RETURN Natural;
  -- Pre:  S is defined
  -- Post: returns the number of members in S

  FUNCTION "<=" (S, T : Set) RETURN Boolean;
  FUNCTION "<"  (S, T : Set) RETURN Boolean;
  -- Pre:  S and T are defined
  -- Post: returns True if and only if S is 
  --   an improper or proper subset of T, respectively

PRIVATE
  TYPE SetArray IS ARRAY (Universe) OF Boolean;
  TYPE Set IS RECORD
    Store: SetArray := (OTHERS => False);
  END RECORD;
  Phi: CONSTANT Set := (Store => (OTHERS => False));
END Sets_Generic;
1 Like

Everything you do with operators will be controversial. imo, given the use of “.” in other languages, php’s solution is least intuitive for me. Now, + means you’re adding two strings together to make one. & means it’s string1 and string2 resultant from that expression. In english, we often use “and” to mean addition, and logic/bitwise interpretations came later. We know bitwise and, logical and, and arithmatic addition cannot logically apply to strings.

Tbh,i don’t know if anyone said it was controversial, but i absolutely do believe it was abuse. Actual books teaching C++ refer to << and >> as “output” and “input” operators respectively, and students would end up very confused if you use them outside of a cout and cin context respectively.

I actually don’t know that one. I’ll have to look it up.

Right. As I said, I get it; it just doesn’t seem as intuitive as +.