Ada-Rapporteur-Group / User-Community-Input

Ada User Community Input Working Group - Github Mirror Prototype
26 stars 1 forks source link

Renames embedded in conditions. #81

Open Blady-Com opened 6 months ago

Blady-Com commented 6 months ago

Let's take a function, its returned result is used both for a condition and for a processing following this condition. For example Ada.Strings.Fixed.Index in Unix path decomposition:

      while Index (Path, "/") > 0 loop
         Delete (Path, Path'First, Index (Path, "/")); -- some processing with position of "/"
      end loop;

The result is used for the condition of the while loop and processing inside the loop.

Another example, the GNAT.OS_Lib.Read function returns the number of characters actually read when reading a file:

      if Read (FD, Buffer'Address, Buffer'Length) < Buffer'Length then
         Put_Line
           ("warning only" & Natural'Image (Read (FD, Buffer'Address, Buffer'Length)) &
            " bytes read."); -- some processing with number of read bytes
      end if;

In the first example, calling Index twice has only a performance impact, but in the second example calling Read twice is erroneous. The solution to these two trivial examples is to declare a local variable to the subroutine. On the other hand, if calling the function requires previously executing code, you must then use a declare statement.

To avoid this heaviness and improve readability, I propose to put a renamed fonction embedded in the condition. First exemple:

      while (Pos renames Index (Path, "/")) > 0 loop
         Delete (Path, Path'First, Pos); -- some processing with position of "/"
      end loop;

Second exemple:

      if (Count renames Read (FD, Buffer'Address, Buffer'Length)) < Buffer'Length then
         Put_Line
           ("warning only" & Natural'Image (Count) &
            " bytes read."); -- some processing with number of read bytes
      end if;

Thus, the fonction is called only once and no need to local variable or declare statement. This renaming could occur for any condition in if and while statement. The scope of the renaming is the if or while statement itself. (Note : it could be present in condition for exit statement but there is no interest.)

Richard-Wai commented 5 months ago

I'm struggling to understand what is "wrong" with the traditional approach of introducing a declare block

declare  Count: constant Natural := Read (FD, Buffer'Access, Buffer'Length);
begin if Count < Buffer'Length then
    Put_Line (".." & Natural'Image (Count)); -- etc
end if; end;

~What's more, in a normal rename we'd need to define the type of the new name, and in this proposal, we'd have to introduce something like the C++ 'auto' type here.~

I'd even go as far as to argue that the proposed syntax would be more suggestive of a subprogram renaming than the renaming of an anonymous return object.

I should note that your proposal still requires the creation of an object to store the result of the call to Read. The only difference is that the traditional way assigns it a name directly, whereas your proposal seems to suggest that some anonymous object be renamed. On the face of it is seems to be fully syntactic sugar that really only allows for slightly improved conciseness. Improved readability is dubious, however..

ARG-Editor commented 5 months ago

Umm, Richard, in Ada 2022 the type name is optional in a renames. So the syntax he is proposing is just that of an ordinary object renames (in an unconventional place).

Speaking for myself, I see a number of issues here:

(1) Not sure why this is restricted to just if statements and while loops. I could imagine such a syntax being useful in case statements, for loops (in the range or object after the in or of), and probably others. The design principle of keeping the language consistent suggests that it should be available as widely as possible.

(2) Introducing a new place where names can be declared means that one has to have new kinds of declarative regions, and thus new places where declarations can be hidden. I would worry that users might get very confused if a name declared in this way hides something else -- this is not a place where experienced Ada programmers are going to look for declarations. This gets worse if this syntax is allowed in the middle of expressions (which it ought to be) as it would increase the level of complication of resolving/understanding expressions. Imagine the new kinds of obfuscations that this would allow:

if Count > Count renames Index (Str, "Pattern") then (Here the first Count is some object declared in an outer scope, and the second is declared here.)

(3) I'm unconvinced that this saves enough over the declare block for the complication of intra-expression declarations. Perhaps a better solution (if one is needed at all) would be to allow an optional declarative part on other compound statements. That would eliminate the extra "begin" and "end" in Richard's example. That is, something like:

    declare
        Count renames Read (FD, Buffer'Access, Buffer'Length);
    if Count < Buffer'Length then
        Put_Line (".." & Natural'Image (Count)); -- etc
    end if;

Note that the renames syntax is already allowed, the only new thing here is allowing a declarative part on an if statement.

(4) Remember to keep in mind Ada's design goals and their consequences. I talked about those in an article on RR's website: http://www.rrsoftware.com/html/blog/consequences.html

                              Randy.
Richard-Wai commented 5 months ago

Umm, Richard, in Ada 2022 the type name is optional in a renames.

Yep I forgot about that!

Perhaps a better solution (if one is needed at all) would be to allow an optional declarative part on other compound >statements. That would eliminate the extra "begin" and "end" in Richard's example. That is, something like:

    declare
        Count renames Read (FD, Buffer'Access, Buffer'Length);
    if Count < Buffer'Length then
        Put_Line (".." & Natural'Image (Count)); -- etc
    end if;

This is actually really interesting!

sttaft commented 5 months ago

One approach is to simply eliminate the need for declare blocks, and like many other languages, permit at least some kinds of declarations pretty much anywhere a statement is permitted. Declarations and statements are largely syntactically distinct from each other in Ada.

AdaCore has prototyped this in GNAT. Here is a writeup of the prototyped feature:

https://github.com/AdaCore/ada-spark-rfcs/blob/master/prototyped/rfc-local-vars-without-block.md
Blady-Com commented 5 months ago

That is, something like:

    declare
        Count renames Read (FD, Buffer'Access, Buffer'Length);
    if Count < Buffer'Length then
        Put_Line (".." & Natural'Image (Count)); -- etc
    end if;

I actually like this too. Let's not forget my first example which may be changed in:

declare
Pos renames Index (Path, "/");
while Pos > 0 loop
Delete (Path, Path'First, Pos); -- some processing with position of "/"
end loop;

Of course, it assumes that the declare part is executed at each turn of the loop that is however less intuitive.