Is this an Ada gotcha?

Assume the following program:

with Ada.Text_IO;
procedure test1 is
  procedure p1(a : integer := 1; b : Integer := 2) is
  begin
    Ada.Text_IO.Put_Line("a,b: " & a'Image & b'Image);
  end p1; 
begin 
   p1(5); 
  -- the original intention was to give b the value 5 but "a" stole it when introduced :-)
end test1;

I have several times modified subroutines and forgotten to check how I have used them. I would prefer that the compiler gave me a hint here.

reinert

It also could warn you that you wanted to call p451 rather than p1! :rofl:

1 Like

This is one of the reasons I always use named notation for procedure calls.

Necessary only when types are same, which is suspicious on its own. Otherwise it is impossible to make an error.
I usually switch to keyed notation when the call occupies more than one source code line,

I’ve seen style rules that require named notation if two or more arguments have the same type. I think AdaControl had a setting to warn on that.

2 Likes

This is the standard behavior I’d expect for languages which have default arguments (python, C++, ruby) – arguments passed fill left to right. You can avoid it by using named arguments, but adding defaults to existing functions is a known issue for those without it.

Yes, good programming habits help :slight_smile:

No, this is not an Ada gotcha, per se..
This is essentially the equivalent of saying ā€œhand me thatā€ and pointing to something, the pan of beans and the teapot both in the line of your pointing, and then getting upset that someone hands you the wrong one even though you were pointing at both.

To do what your comment indicates you want, be specific, say P1( B => 5 );.

I’ve seen style rules that require named notation if two or more arguments have the same type

This is the rule that I personally follow. Whenever I find myself passing two parameters of the same type, one after another, a little bell goes off in my head and I switch over to named notation. In fact I generally try to avoid designing subprograms with two formal parameters in a row with the same type.

The other rule I follow is to use named notation for every parameter that has a default value.

And then the simplest rule is to just use named notation everywhere … :wink:

3 Likes

I think I understand.

However, if you could analyse some million lines of code around, how many mistakes could it be due to this ā€œfeatureā€/ā€œissueā€ ? Zero?

That really depends, if you designed it with the defaults, all/majorly up-front design, testing/verifying, the chance isn’t too high…OTOH, if you’re seat-of-you-pants/iterative developing, much higher.

The issue is essentially this:

 Procedure Example( X, Y, Z : Integer );  -- Rev. 1
 Procedure Example( X, Y    : Integer );  -- Rev. 2
 Procedure Example( X       : Integer );  -- Rev. 3

There’s three procedures here, they could be the same (feeding default values into the overloaded procedure with fewer parameters), or they could be different. Whereas with Procedure Example_2( X, Y : Integer:= 1; Z : Integer := 0 ); you know exactly that the former is happening for all permutations. – The feature (default parameters [or even overloading]) is not a bad feature, it’s not even particularly error-prone (see this story; the only bug was because someone he queried for the proper sequence guessed instead of checking the information).

The TL;DR here is this: if you are being bitten by defaults even semi-regularly, then it means that you are not spending enough time designing, because those bugs only happen when the design is fluctuating.

Do we end up with a blame game here? :slight_smile:

No, not really.
It’s not ā€œthe blame gameā€ to acknowledge that (1) a ever-fluctuating ā€˜soft’ design will be inconsistent, and (2) changing said design impacts how things interact.

1 Like

An additional advantage of using named arguments is that client code becomes shielded against (same type) formal arguments being shifted around at unsuspecting moments by some maintainer who decides a different declaration order is clearly more coherent and long overdue..

1 Like

If this were an ā€œAda gotchaā€, I’d expect g++/clang++ to guess my intention when compiling the code below.

#include <iostream>

void p1(int a = 1, int b = 2)
{
   std::cout << "a,b:  " <<  a << ' ' << b << '\n';
}

int main()
{
   p1(5);  // a,b:  5 2
}

Sadly, they choose to ignore my thinking completely.

I tried python…

def p1(a = 1, b = 2):
    print(f"a,b:  {a} {b}")
p1(5)  # a,b:  5 2

No matter what I do, I get got, it seems.

(Ada at least let’s you name the actual parameters at call site but you’ve already heard that chorus.)

1 Like

The equivalent in OCaml is:

let p1 ?(a:int=1) ?(b:int=2) () =
  print_endline ("a, b: " ^ (string_of_int a) ^ " " ^ (string_of_int b))
in
p1 5 ()

The compiler responds with:

4 | p1 5 ()
         ^^
Error: The function applied to this argument has type
         ?a:int -> ?b:int -> unit
This argument cannot be applied without label

To get it to compile, replace the line p1 5 () with p1 ~b:5 ().

Another potential mistake is to supply two arguments in the wrong order (e.g., b, then a) when not using labels.

So, it could be useful to have the style mentioned by @JeremyGrosser checked by the compiler or by a linter.

I’ve seen style rules that require named notation if two or more arguments have the same type. I think AdaControl had a setting to warn on that.