Constant element in Ada object? - ada

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).

Related

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.

Wrapping an imported C pointer in an Ada tagged type of the same size

I'd like to replicate a C struct memory layout into an Ada type, and at the same time wrap the C fields (which are pointers) into tagged types that are at the same memory locations as the pointers to avoid extra memory use, while using dot notation on the pointee information in a confortable Ada way. This requires that the tagged Ada fields have the same size as the C pointers.
An example that works without tagged types is as follows:
with Ada.Text_IO; use Ada.Text_IO;
procedure Tag is
package P is
type Int_Ptr is access all Integer with Convention => C;
-- This would be in reality a C record
type Rec is record
I : Int_Ptr := new Integer'(42);
end record
with Convention => C;
-- This is the field wrapper that would be tagged
type Wrap_I is record -- Making this tagged breaks it
I : Int_Ptr;
end record
with Convention => C;
function Image (WI : Wrap_I) return String is
(WI.I.all'Img);
-- This is the Ada type corresponding to C Rec
type Wrap_Rec is record
I : Wrap_I;
end record
with Convention => C;
end P;
R : P.Rec;
W : P.Wrap_Rec with Address => R'Address, Import;
begin
Put_Line (P.Image (W.I)); -- Should be 42 if properly overlaid
-- This is the objective: to use dot notation on C pointed data:
-- Put_Line (W.I.Image);
end Tag;
Now, the objective is to make Wrap_I tagged. Once I mark it as such, GNAT warns that R and W sizes differ (and it raises a tag check failed at runtime, obviously). I do not need dispatching or anything but static calls, so in essence I wonder if there is a way of having a tagged type without storing its tag in memory (to be used statically only).
I have a much more traditional plan B so no need to suggest alternatives if this is not doable.
Thanks!
You can't have tagged types without tags, so it isn't doable.

Can't understand how generics work

I have a package called Linked_List(.ads) Here is the code in it:
Generic
type T is private;
package Linked_List is
type List is tagged record
Data : T;
end record;
end Linked_List;
and here is the code in the package which contains the main function (main.adb)
with Ada.Text_IO; use Ada.Text_IO;
with Linked_List;
procedure Main is
type ListOfIntegers is new Linked_List(T => Integer);
begin
null;
end Main;
I'm getting this error:
4:30 subtype mark required in this context
found "Linked_List" declared at linked_list.ads:3
found "Linked_List" declared at linked_list.ads:3
4:41 incorrect constrain for this kind of type
Any help is appreciated.
new Linked_List(T => Integer) defines a new package, not a new type. The error messages you’re getting are because the compiler thinks you’re declaring a type, so seeing the name of a package at column 30 confused it; it wanted to see the name of a (sub)type.
Line 4 should read
package ListOfIntegers is new Linked_List(T => Integer);
after which there is a type ListOfIntegers.List, so you can write
My_List : ListOfIntegers.List;
You may find having to say ListOfIntegers. all the time annoying; you can say
use ListOfIntegers;
after which you can just write
My_List : List;
but it’s usually thought best not to overdo this (if you have dozens of “withed” packages, “using” them all makes it difficult to know which one you’re referring to).
By the way, normal Ada usage is to use underscores to separate words in identifiers: List_Of_Names.

What is "Subtype mark required in this context" exactly?

I get Subtype mark required in this context at (*). What exactly is subtype mask and why is it complaining here?
main.adb
(*)Open_Route : Route(1..3) := (others => new Location(X=>1.0,Y=>1.0, id=>1));
-- Closed_Route : Route (Open_Route'First .. Open_Route'Last + 1);
-- P1 : Population (1..2);
Location.ads package spec
type Location is record
Id : Positive;
X : Float;
Y : Float;
end record;
type Location_Acess is access all Location;
type Route is array (Positive range<>) of Location_Acess;
type Route_Acess is access all Route;
type Population is array (Positive range<>) of Route_Acess;
A subtype_mark is basically a name that denotes a type or subtype (ARM 3.2.2(4)). The reason why the ARM uses subtype_mark throughout is to do with some arcane distinction (ARM 3.2.1); when you say type Foo is you are declaring both a type and its first subtype, and the name Foo refers to the first subtype.
I think your problem is that you have a package Location containing an entity of the same name, and that this has confused the compiler.
You haven’t supplied a complete compilable code example, but reading between the lines I think it’d be something like this, after applying #ajb’s suggestion:
package Location is
type Location is record
Id : Positive;
X : Float;
Y : Float;
end record;
type Location_Acess is access all Location;
type Route is array (Positive range<>) of Location_Acess;
type Route_Acess is access all Route;
type Population is array (Positive range<>) of Route_Acess;
end Location;
with Location; use Location;
package Location_User is
Open_Route : Route(1..3) := (others => new Location'(X=>1.0,Y=>1.0, id=>1));
end Location_User;
and the compiler error message would be
location_user.ads:3:47: subtype mark required in this context
location_user.ads:3:47: found "Location" declared at location.ads:1
(see how it’s telling you to look at line 1 of location.ads, the package name?)
You could work round this by saying new Location.Location, but then you’d need to say it everywhere.
There are two canonical solutions, and there is a religious divide between the supporters of the two camps.
The first (my preferred one) would be to call the package Locations and leave the types Location, Route, Population as they are.
The second (which I consider ugly and would only use in dire need) would be to decorate type names with a suffix:
type Location_Type is
or
type Location_T is
The source code given in the question is not complete (you did not state whether you used or merely withed the Location package. I assume the latter since (at least with the compiler I have installed) the error message given is invalid constraint: type has no discriminant.
#ajb has already pointed out the solution to that problem: without the quote mark, the compiler looks for a type with a discriminant (a type parameterised by another type), but Location does not have a discriminant.
Moreover, unless you used the Location package, new Location does not actually refer to a type (or subtype), but to a package. If you do not want to use Location, type
new Location.Location'(… here. One way to avoid such errors (and get better error messages) is to use different names for packages and type: when both have the same name, it is not always intuitively clear how the compiler interprets the occurrence of the name, and when the opinions of compiler and programmer differ, confusion ensues.
Personally, I like to use the plural for for the package, so in this case, there would be a package Locations containing the type Location, but there are other possibilities.

The use of IN OUT in Ada

Given below is some code in ada
with TYPE_VECT_B; use TYPE_VECT_B;
Package TEST01 is
procedure TEST01
( In_State : IN VECT_B ;
Out_State : IN OUT VECT_B );
function TEST02
( In_State : IN VECT_B ) return Boolean ;
end TEST01;
The TYPE_VECT_B package specification and body is also defined below
Package TYPE_VECT_B is
type VECT_B is array (INTEGER range <>) OF BOOLEAN ;
rounded_data : float ;
count : integer ;
trace : integer ;
end TYPE_VECT_B;
Package BODY TYPE_VECT_B is
begin
null;
end TYPE_VECT_B;
What does the variable In_State and Out_State actually mean? I think In_State means input variable. I just get confused to what actually Out_State means?
An in parameter can be read but not written by the subprogram. in is the default. Prior to Ada 2012, functions were only allowed to have in parameters. The actual parameter is an expression.
An out parameter implies that the previous value is of no interest. The subprogram is expected to write to the parameter. After writing to the parameter, the subprogram can read back what it has written. On exit the actual parameter receives the value written to it (there are complications in this area!). The actual parameter must be a variable.
An in out parameter is like an out parameter except that the previous value is of interest and can be read by the subprogram before assignment. For example,
procedure Add (V : Integer; To : in out Integer; Limited_To : Integer)
is
begin
-- Check that the result wont be too large. This involves reading
-- the initial value of the 'in out' parameter To, which would be
-- wrong if To was a mere 'out' parameter (it would be
-- uninitialized).
if To + V > Limited_To then
To := Limited_To;
else
To := To + V;
end if;
end Add;
Basically, every parameter to a function or procedure has a direction to it. The options are in, out, in out (both), or access. If you don't see one of those, then it defaults to in.
in means data can go into the subroutine from the caller (via the parameter). You are allowed to read from in parameters inside the routine. out means data can come out of the routine that way, and thus you are allowed to assign values to the parameter inside the routine. In general, how the compiler accomplishes the data passing is up to the compiler, which is in accord with Ada's general philosophy of allowing you to specify what you want done, not how you want it done.
access is a special case, and is roughly like putting a "*" in your parameter definition in Cish languages.
The next question folks usually have is "if I pass something large as an in parameter, is it going to push all that data on the stack or something?" The answer is "no", unless your compiler writers are unconsionably stupid. Every Ada compiler I know of under the hood passes objects larger than fit in a machine register by reference. It is the compiler, not the details of your parameter passing mechanisim, that enforces not writing data back out of the routine. Again, you tell Ada what you want done, it figures out the most efficient way to do it.

Resources