subprogram declarative section order of evaluation Ada - ada

With the declarative section of subprograms in Ada, is the order in which variable assignments is evaluated the order in which they are declared? For example if I write
procedure Foo() is
I: Integer := 4;
J: Integer := I * 2;
begin
--do stuff
end Foo;
can I say for certain that I will always be evaluated before J?

It seems to me like declarative items are indeed evaluated in order, according to ARM 3.11(7):
The elaboration of a declarative_part consists of the elaboration of the declarative_items, if any, in the order in which they are given in the declarative_part.
Edited: Source of ARM

Related

Ada: Re-exporting enum type values

I have an ada package in the form of
package C is
type Test is (Test_1, Test_2, Test_3);
end C;
and another package in the form of
with C;
package B is
subtype Test is C.Test;
end B;
and yet another in the form of
with B;
package A is
subtype Test is B.Test;
Item: Test := Test_1;
end A;
Naively, I would expect that a subtype defined in B, which is later subtyped by A, would be able to access members of the original type. However, upon inspection, the members of C are not even visible in the scope of B. This can be fixed by adding use c;, this seems like a find solution to a degree, however to use it in A, you would have to add with c; use c; to every package that transitively depends on C. This could lead to confusion as it wouldn't be implicitly obvious that you should be using C.
I would like to be able to "rexport" these types, so that I can abstract layers of my program better.
If you change package A to
with B;
package A is
subtype Test is B.Test;
use all type Test;
Item: Test := Test_1;
end A;
the code compiles. Not sure whether this will help.
This is an Ada 2012 feature; see ARM 8.4(8) and (8.1). use all type makes the primitive operations of the type visible (which includes enumeration literals).
If you really want to re-export the values, you can do this:
with C;
package B is
subtype Test is C.Test;
function Test_1 return Test is (C.Test_1);
function Test_2 return Test is (C.Test_2);
function Test_3 return Test is (C.Test_3);
end B;
Unfortunately, you can't use named numbers since enumerations are not numeric types. You can make these functions normal constants instead, but conceptually, this would execute code at elaboration time (the compiler will probably optimize it away, but you can't use pragma Preelaborate anymore).
This allows you to access the literals in A using B.Test_1 etc. This is also a proper abstraction as A will not depend on the literals defined in C anymore (you can rename the literals in C without affecting A, you do need to update B though to map the new names to the original ones).
Simply importing the literal names into the namespace of A is not an abstraction.
with B;
package A is
type Test is new B.Test;
Item: Test := Test_1;
end A;
...and all is well!
Ada has the concept of visibility, with a lot of rules that define when, where, and how things are visible. Understanding visibility is key to understanding Ada. One of the best discussions of visibility is in Ada Distilled. What you're encountering is a consequence of the visibility rules. This goes even further than enumeration literals:
package Pack is
type Int is range -(2 ** 15) + 1 .. 2 ** 15 - 1;
end Pack;
with Pack;
procedure Proc is
subtype Int is Pack.Int;
-- None of the operators of Int ("+", "*", ...) are directly visible here
begin
null;
end Proc;
Declarations in a package spec are only visible outside the package by using dotted notation, unless you explicitly make them directly visible elsewhere. use clauses are one way to make them visible; others are renames and, for enumeration literals, declaring constants of the type initialized using dotted notation.

Does Ada have any idiomatic rules on when to use a function versus a procedure with an output parameter?

You can assign to a variable by having a function return a value to it:
My_Int : Integer := My_Math_Func [(optional params)];
Or you can do it like this with a procedure (assuming My_Int has already been declared):
My_Math_Proc ([optional params;] [in] out My_Int);
Obviously a procedure can't initialize a variable like the function does in the first example, but I'm hoping for some concrete, practical rules on when and why to pick one over the other.
Two to get you started...
When more than one result is to be returned, a procedure with several OUT parameters is often a good choice.
When the size of the object is unknown prior to the subprogram call, an OUT parameter cannot be used because it would have to be declared precisely the right size, but a function return can set the size by initialising the variable in the caller. This is commonly used with a variable declared in a Declare block, which can hold a different sized string each time it is invoked.
This example shows the variable "text" initialised by calling a Read_File function, to hold the contents of a different file on each iteration of the loop. Safe, no "malloc" or "free" or pointers necessary. (Filename is an array of filenames in this example)
for i in 1 .. last_file loop
declare
text : String := Read_File(Filename(i));
-- the size of "text" is determined by the file contents
begin
-- process the text here.
for j in text'range loop
if text(j) = '*' then
...
end loop;
end
end loop;
Edit : And I suppose I'd better mention the underlying mathematical principle, since Ada is based more closely on mathematical logic than many other languages.
Functions and procedures are both subprograms, but for different purposes:
a function is an abstraction over an expression : like a mathematical operator (and an operator in Ada is just a function). Ideally, it provides a result from a number of operands and nothing else, leaving them unchanged and having no state and no side effects. This ideal is called a "pure function" (and applying "pragma pure" asks the compiler to check its purity) - similar restrictions apply in functional programming (FP) languages. Pure functions allow a whole bunch of optimisation (because reordering them doesn't change results). In practice Ada is not that strict, allowing impure functions too.
a procedure is an abstraction over a statement. It generally has some physical effect (such as changing state) since it doesn't deliver a result.
So the logical separation between expressions and statements is carried over into subprograms (abstractions) as the separation between functions and procedures.
And this is probably the best way to decide which to use.
Brian Drummond already answered your question directly, but I wanted to add some additional info: If your type has some sort of initializing procedure, in Ada2005/Ada2012 you can convert it to an initializing function using extended return syntax. It will even work for limited types.
Say you have a package with a type like this:
package Example is
type My_Type is limited private;
procedure Initialize(Self : in out My_Type; Value : Integer);
procedure Print(Self : My_Type);
private
type My_Type is limited record
Value : Integer := 0;
end record;
end Example;
package body Example is
procedure Initialize(Self : in out My_Type; Value : Integer) is
begin
Self.Value := Value;
end Initialize;
procedure Print(Self : My_Type) is
begin
Ada.Text_IO.Put_Line(Self.Value'Image);
end Print;
end Example;
You can then make your own initializing function out of that procedure doing something like this:
function Make_My_Type (Value : Integer) return Example.My_Type is
begin
return Result : Example.My_Type do
Example.Initialize(Result,Value);
end return;
end Make_My_Type;
and you can initialize variables easily using the procedure hidden in your function underneath:
procedure Test
Thing : Example.My_Type := Make_My_Type(21);
begin
Example.Print(Thing);
end Test;
This is different than just making a variable and returning it. You are not able to do that with a limited type, but with extended return syntax, you can do it for any type.
Here is some additional info for extended return statements as well.

What style checking does -gnatyo actually perform?

The manual reads:
Check order of subprogram bodies. If the letter o appears in the string after -gnaty then all subprogram bodies in a given scope (e.g. a package body) must be in alphabetical order. The ordering rule uses normal Ada rules for comparing strings, ignoring casing of letters, except that if there is a trailing numeric suffix, then the value of this suffix is used in the ordering (e.g. Junk2 comes before Junk10).
I take it that Junk2 coming for Junk10 is the actual inspiration for an otherwise odd style enforcement. But what's an example of some code that actually triggers it? I couldn't get this option to complain with disordered function definitions or task bodies. For example, I get no complaints when compiling the following with gnat make -gnatyo:
procedure Disordered is
function Test return Natural;
function Zest return Natural;
-- disordered function bodies
function Zest return Natural is (1);
function Test return Natural is (2);
begin
null;
end Disordered;
You have:
-- disordered function bodies
function Zest return Natural is (1);
function Test return Natural is (2);
but technically, these are not subprogram bodies. They are called expression functions. The reason for this clear separation is that subprogram bodies are not allowed in package specifications, whereas expression functions are.
Using actual subprogram bodies in your example will give the expected style warning:
function Zest return Natural is
begin
return 1;
end Zest;
function Test return Natural is
begin
return 2;
end Test;
(and since you mentioned task bodies; those are also not subprogram bodies)

Constant element in Ada object?

In Java or C#, you would often have class members that are final or readonly - they are set once and then never touched again. They can hold different values for different instances of the class.
Is there something similar in Ada? I've tried to create something similar in Ada thusly:
package MyPackage is
type MyObject is limited new OtherPackage.Object with private;
....
private
type MyObject (...) is limited new OtherPackage.Object with
record
M_MyField : Integer := 10;
M_MyConstantFactory : constant Factory.Object'Class := new Factory.Object;
end record;
end MyPackage;
This fails on the declaration of M_MyConstantFactory saying constant components are not permitted. Is there a way around this? A colleague suggested declaring it somewhere else in the package, but that would mean a single M_MyConstantFactory shared across all instances, which is not what I want.
Do I need to just accept that it is possible to modify the value once set and manually guard against that happening?
No. Not quite.
If your component is of a discrete type or an access type, you can make it a discriminant, and thus make it immutable.
with Ada.Integer_Text_IO;
procedure Immutable_Components is
type Instance (Immutable : Positive) is null record;
A : Instance := (Immutable => 1);
begin
Ada.Integer_Text_IO.Put (A.Immutable);
-- A.Immutable := 2; -- assignment to discriminant not allowed:
end Immutable_Components;
Before I answer the question, it would probably be helpful to distinguish between Ada and Java/C#'s modeling of objects. In Java, everything is an object, and so all constants have to be final -- in Ada things are a bit different, Ada's object-system ("tagged types" in Ada parlance) is built upon two items: records and type-derivation. This means that, when teaching OOP, we could arrive gradually introducing first type-derivation (eg Type Degree is new Integer;), then records (ie encapsulation), then private-types (ie information-hiding), and finally unifying everything together with tagged-types... all of which I'll assume you are cognizant of.
In Ada, a constant is just that: some object that can be read but [generally] not written to. (Things can get funny with eg memory-mapped IO.) So we could say:
Package Ex1 is
Type Stub1 is private; -- Some type, with almost nothing public.
C1 : Constant Stub1; -- A constant of that type.
Private
Type Stub1 is tagged record
Data_1 : Integer;
Data_2 : Float;
end;
-- And now we can tell the compiler what C1 _is_.
C1: Constant Stub1 := (Data_1 => 3, Data_2 => 1.2);
End Ex1;
That's how we'd make a constant for a tagged type while keeping its implementation details hidden; though, admittedly, we could have exposed everything and gotten rid of the entire private section.
Now we get to an interesting feature of records [and tagged types] called discriminants -- these are kind of like constants, and kind of like generic-types in other languages. With discriminants we could make a message-type that varies in size according to the message-length:
Package Ex2 is
Type Message(Length : Natural) is private; -- A message.
Function Create( Text : String ) return Message;
Private
Type Message(Length : Natural) is record
Data : String(1..Length) := (Others => ' '); -- Defaults to space-filled string.
end;
Function Create( Text : String ) return Message is
( Data => Text, Length => Text'Length );
End Ex2;
Now, in this case, when you do an assignment like X : Message := Create("Steve"); the variavle's type [unconstrained, in this example, becomes constrained in this case to Message(5) (because "Steve" is 5 characters) and so trying to re-assign with a different-sized message-string wouldn't work. (So, while you couldn't say X:= Create("Why") you can say X:= Create("Hello") because the discriminant [Length] here is 5.) -- So, in that manner discriminants can act like constant fields in some cases.
The limited keyword means that the type doesn't have an assignment [but does have initialization], so you could make the entire type behave as a constant; this is different than having the one component be constant though, certainly not as subtle as the distinction between T and T'Class (T'Class is the Type T and all types derived therefrom, where as T is only that type.)
You almost have the general solution (for cases when it can't be a discriminant) already. The type is limited private. Clients of the package can only modify it through the operations the pkg provides. As long as the operations don't modify the field in question, you have what you want (unless I misunderstood and the question is how to prevent yourself from modifying the field in the pkg body).

ada - declarations must come before begin?

Question: why and how to fix this error message about ADA? Thax
12:04 declarations must come before "begin"
29:01 statement expected
With Ada.Text_IO; Use Ada.Text_IO;
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;
With Ada.Strings.Unbounded; Use Ada.Strings.Unbounded;
With Ada.Strings.Bounded;
procedure polynomial is
begin
function evaluate (x: Integer) return Integer is
type i is new Integer;
type p is new Integer;
type x is new Integer;
begin
for i in reverse 0..10 loop
i:= i-1;
p:= coef(i)+x*p;
end loop;
return p;
end evaluate;
end polynomial;
As the error message says, declarations must come before begin. Thus, if you have a declaration, it must come before the begin of the construct that encloses it. For example, the type declarations of i, x, and p (which should not be type declarations, but that's another problem) are directly enclosed in the evaluate function. Therefore, they must appear before the begin of the evaluate function, as they are.
The thing is that this applies to function and procedure declarations, too--and a complete function body, such as evaluate, counts as a declaration for this rule. evaluate is directly enclosed in polynomial. Therefore, it must appear before the begin line belonging to polynomial, whereas your code puts it right after the begin line, which is illegal.
There are actually two ways to fix it: (1) Move evaluate before the begin line. (2) Add a block that begins with declare:
procedure polynomial is
begin
declare
function evaluate (x: Integer) return Integer is
i : Integer;
-- etc.
begin
-- etc.
end evaluate;
begin -- this is the BEGIN line of the block
-- Now put some code here!
end; -- this is the end of the block
end polynomial;
Now the evaluate function is directly enclosed in the block, not in polynomial, and it must occur before the begin line of the block, but it doesn't have to occur before the begin line of polynomial.
A couple other things: This declaration:
type i is new Integer;
does not mean that "the type of i is an integer". It means you're declaring a new type named i. You don't want that, you want a variable.
i : Integer;
Second: note the part of the block where I said "now put some code here". That's necessary. Declaring the function evaluate does not run the function. You have to put in a statement that calls it, if you ever want your function to run. Your original incorrect code didn't have any statements that called evaluate, which is why I am not sure whether you understood that concept.

Resources