I am going through the learn.adacore.com tutorial and have hit upon a problem that I am unsure of.
Specifically I understand that Ada is designed to trap attempts to overflow a variable with a specified range definition.
In the case below, the first attempt to do this causes a compiler 'range check failure' which is expected. However the following line doesn't trap it and I am not sure why:
with Ada.Text_IO; use Ada.Text_IO;
procedure Custom_Floating_Types is
type T_Norm is new float range -1.0 .. 1.0;
D : T_Norm := 1.0;
begin
Put_Line("The value of D =" & T_Norm'Image(D));
-- D := D + 1.0; -- This causes a range check failure at run time = completely expected.
Put_Line("The value of D =" & T_Norm'Image(D + 1.0)); -- This doesn't?
end Custom_Floating_Types;
You have a couple of pretty good answers, but I'm going to add another because it's clear that you expect the expression D + 1.0 to raise an exception, and the answers you have don't explain why it doesn't.
A type declaration like
type T_Norm is new float range -1.0 .. 1.0;
is roughly equivalent to
type T_Norm'Base is new Float;
subtype T_Norm is T_Norm'Base range -1.0 .. 1.0;
The type (called the "base type") isn't given a name, though it can often be referenced with the 'Base attribute. The name that is given is to a subtype, called the "first-named subtype".
This distinction is important and is often not given enough attention. As explained by egilhh, T_Norm'Image is defined in terms of T_Norm'Base. This is also true of the arithmetic operators. For example, "+" is defined as
function "+" (Left : in T_Norm'Base; Right : in T_Norm'Base) return T_Norm'Base;
2.0 is clearly in the range of T_Norm'Base, so evaluating D + 1.0 doesn't violate any constraints, nor does passing it to T_Norm'Image. However, when you try to assign the resulting value to D, which has subtype T_Norm, a check is performed that the value is in the range of the subtype, and an exception is raised because the check fails.
This distinction is used in other places to make the language work reasonably. For example, a constrained array type
type A is array (1 .. 10) of C;
is roughly equivalent to
type A'Base is array (Integer range <>) of C;
subtype A is A'Base (1 .. 10);
If you do
V : A;
... V (2 .. 4)
you might expect problems because the slice doesn't have the bounds of A. But it works because the slice doesn't have subtype A but rather the anonymous subtype A'Base (2 ..4).
The definition of 'Image says:
For every scalar subtype S:
S'Image denotes a function with the following specification:
function S'Image(Arg : S'Base)
return String
As you can see, it takes a parameter of the base (unconstrained) type
T_Norm'Image(D + 1.0) neither assigns nor reads an out-of-range value. It asks for the 'Image attribute (string representation) of (D + 1.0), which is the same as asking for (1.0 + 1.0).
I can see two ways the confusion might arise. First, the name "attribute" could be misinterpreted to suggest that 'Image involves something intrinsic to D. It doesn't. The 'Image attribute is just a function, so D is just part of the expression that defines the value of the parameter (in your example = 2.0).
Second, the 'Image attribute comes from Float. Thus, any Float can be its parameter.
You can create a function that accepts only parameters of T_Norm'Range and make that function an attribute of T_Norm. See Ada Reference Manual 4.10.
Because you don't store a value greater than range in D.
Related
Let the following Ada types be defined:
type Type_Huge is array (1 .. Integer'Last) of Float;
type Type_B is (foo, bar, blop, bloub);
type Type_A ( disc : Type_B := foo) is
record
case disc is
when foo =>
My_int : Integer;
when bar =>
huge_data : Type_Huge := (others => 0.0);
when others =>
null;
end case;
end record;
1- Do you confirm the following ?
my_var : Type_A;
(Type_A'Size = my_var'Size) returns False
2- what is the real value of my_var'Size ?
I would say the size is at least:
Type_B'Size + Integer'Size
3- What is the value of Type_A'Size ?
I would say the size is the max of the possible configurations.
3- Is there anything else the compiler would add (probably hidden) to my_var?
I have also read some articles concerning Value_Size and Object_Size
But I don't get the full picture right now.
Thx
As quoted in another answer the LRM requires 'Size be defined by the implementation for indefinite types; in addition, LRM M.2 (45) requires that the implementation documents this characteristic:
(45) The meaning of Size for indefinite subtypes.
If your compiler is GNAT, this is what it states in its reference manual:
Size for an indefinite subtype is the maximum possible size,
(...).
You can see the compiler's choice when you add compiler switch -gnatR3. The output also lists numbers for 'Value_Size as they depend on the record discriminant's value. (The (...) part talks about sizes of subprogram parameters.)
Quoting section 13.3 in the LRM:
(44) For every subtype S:
(45) S'Size [...]
(48) If S is indefinite, the meaning is implementation defined. [...]
In other words: It is implementation defined.
Difference between out mode and in out mode? As far i have collected the info is
the main difference i know is that in both out and in out mode the actual parameter is expected to be altered even writing and reading is also possible then what is main difference please help me to understand?
The best explanation of the difference is in RM 6.4.1(11-15).
Semantically, there is a difference only for parameters that are passed by copy. If you call a subprogram that has an in out parameter, the actual parameter is expected to have a legitimate value before the call. On entry, the subprogram makes a copy of the variable and checks to make sure it satisfies the constraints.
For an out parameter, the actual parameter does not need to have a value going in; it could be uninitialized garbage. The intent is that the subprogram will not use the value of this parameter until it has been set, by the subprogram (it can be set either by assignment or by being passed as an out parameter to some other subprogram, or to a Default_Value, or perhaps by other means). This is not enforced, however.
This can cause different behavior in some cases. For example:
subtype Int10 is Integer range 1 .. 10;
procedure Proc (Param : in out Int10) is
begin
Param := 5;
end Proc;
Y : Integer := 100;
...
Proc (Y);
Since Param is an in out parameter, the constraints are checked on entry. Thus, the call to Proc(Y) raises a Constraint_Error. However:
subtype Int10 is Integer range 1 .. 10;
procedure Proc (Param : out Int10) is
begin
Param := 5;
end Proc;
Y : Integer := 100;
...
Proc (Y);
In this case, no Constraint_Error is raised. In fact, Y does not need to be initialized at all, because the expectation is that the procedure will not use the input value. According to the RM, the value is not even copied, for certain classes of parameter types. So in this case:
Save_Param : Integer;
procedure Proc (Param : out Int10) is
begin
Save_Param := Param; -- legal but not recommended
Param := 5;
end Proc;
Y : Integer := 3;
...
Proc (Y);
Save_Param will probably be set to some garbage value, not 3. (In Ada 2012, there is a Default_Value aspect that can be applied to subtypes; in a case like that, an out parameter would be set to this default value instead of uninitialized garbage, while an in out parameter would still get the value from the actual parameter.)
For parameters that are passed by reference, there really is no difference in behavior.
The rules in Ada 83 were different. A subprogram with an in out parameter could both read and write that parameter, while a subprogram with an out parameter was allowed to assign to the parameter but could not do anything that would read that parameter's value (barring some cases where there was no way around reading discriminants). Thus:
procedure Proc (Param : out Int10) is
begin
Param := 5;
Save_Param := Param; -- illegal in Ada 83, legal in Ada 95 and later versions
end Proc;
In other words, an out parameter really was output-only. The rule was relaxed in Ada 95, though, as programmers found it too restrictive.
For a list, you can do pattern matching and iterate until the nth element, but for a tuple, how would you grab the nth element?
TL;DR; Stop trying to access directly the n-th element of a t-uple and use a record or an array as they allow random access.
You can grab the n-th element by unpacking the t-uple with value deconstruction, either by a let construct, a match construct or a function definition:
let ivuple = (5, 2, 1, 1)
let squared_sum_let =
let (a,b,c,d) = ivuple in
a*a + b*b + c*c + d*d
let squared_sum_match =
match ivuple with (a,b,c,d) -> a*a + b*b + c*c + d*d
let squared_sum_fun (a,b,c,d) =
a*a + b*b + c*c + d*d
The match-construct has here no virtue over the let-construct, it is just included for the sake of completeness.
Do not use t-uples, Don¹
There are only a few cases where using t-uples to represent a type is the right thing to do. Most of the times, we pick a t-uple because we are too lazy to define a type and we should interpret the problem of accessing the n-th field of a t-uple or iterating over the fields of a t-uple as a serious signal that it is time to switch to a proper type.
There are two natural replacements to t-uples: records and arrays.
When to use records
We can see a record as a t-uple whose entries are labelled; as such, they are definitely the most natural replacement to t-uples if we want to access them directly.
type ivuple = {
a: int;
b: int;
c: int;
d: int;
}
We then access directly the field a of a value x of type ivuple by writing x.a. Note that records are easily copied with modifications, as in let y = { x with d = 0 }. There is no natural way to iterate over the fields of a record, mostly because a record do not need to be homogeneous.
When to use arrays
A large² homogeneous collection of values is adequately represented by an array, which allows direct access, iterating and folding. A possible inconvenience is that the size of an array is not part of its type, but for arrays of fixed size, this is easily circumvented by introducing a private type — or even an abstract type. I described an example of this technique in my answer to the question “OCaml compiler check for vector lengths”.
Note on float boxing
When using floats in t-uples, in records containing only floats and in arrays, these are unboxed. We should therefore not notice any performance modification when changing from one type to the other in our numeric computations.
¹ See the TeXbook.
² Large starts near 4.
Since the length of OCaml tuples is part of the type and hence known (and fixed) at compile time, you get the n-th item by straightforward pattern matching on the tuple. For the same reason, the problem of extracting the n-th element of an "arbitrary-length tuple" cannot occur in practice - such a "tuple" cannot be expressed in OCaml's type system.
You might still not want to write out a pattern every time you need to project a tuple, and nothing prevents you from generating the functions get_1_1...get_i_j... that extract the i-th element from a j-tuple for any possible combination of i and j occuring in your code, e.g.
let get_1_1 (a) = a
let get_1_2 (a,_) = a
let get_2_2 (_,a) = a
let get_1_3 (a,_,_) = a
let get_2_3 (_,a,_) = a
...
Not necessarily pretty, but possible.
Note: Previously I had claimed that OCaml tuples can have at most length 255 and you can simply generate all possible tuple projections once and for all. As #Virgile pointed out in the comments, this is incorrect - tuples can be huge. This means that it is impractical to generate all possible tuple projection functions upfront, hence the restriction "occurring in your code" above.
It's not possible to write such a function in full generality in OCaml. One way to see this is to think about what type the function would have. There are two problems. First, each size of tuple is a different type. So you can't write a function that accesses elements of tuples of different sizes. The second problem is that different elements of a tuple can have different types. Lists don't have either of these problems, which is why you can have List.nth.
If you're willing to work with a fixed size tuple whose elements are all the same type, you can write a function as shown by #user2361830.
Update
If you really have collections of values of the same type that you want to access by index, you should probably be using an array.
here is a function wich return you the string of the ocaml function you need to do that ;) very helpful I use it frequently.
let tup len n =
if n>=0 && n<len then
let rec rep str nn = match nn<1 with
|true ->""
|_->str ^ (rep str (nn-1))in
let txt1 ="let t"^(string_of_int len)^"_"^(string_of_int n)^" tup = match tup with |" ^ (rep "_," n) ^ "a" and
txt2 =","^(rep "_," (len-n-2)) and
txt3 ="->a" in
if n = len-1 then
print_string (txt1^txt3)
else
print_string (txt1^txt2^"_"^txt3)
else raise (Failure "Error") ;;
For example:
tup 8 6;;
return:
let t8_6 tup = match tup with |_,_,_,_,_,_,a,_->a
and of course:
val t8_6 : 'a * 'b * 'c * 'd * 'e * 'f * 'g * 'h -> 'g = <fun>
I am attempting to understand how to fix this circular dependency. All the examples I can find online suggest using a limited with, but then they demonstrate the use with two basic types, whereas this is a bit more advanced. The circular dependency is between the two files below. I thought it was between package Chessboard ... and the Piece type, but now I am not so sure. Attempting to put the package Chessboard ... line within chess_types.ads after the Piece type is declared and removing the use and with of Chessboard results in an error: this primitive operation is declared too late for the Move procedure. I am stuck on how to get out of this dependency. Any help would be much appreciated!
Thank you
chessboard.ads:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
with Chess_Types;
use Chess_Types;
package Chessboard is new Indefinite_Vectors(Board_Index, Piece'Class);
chess_types.ads:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
with Chessboard;
use Chessboard;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is tagged
record
Name : String (1 .. 3) := " ";
Alive : Boolean := False;
Team : Color;
Coordinate : Integer;
end record;
procedure Move_Piece(Board: in Vector; P: in Piece; Move_To: in Integer);
end Chess_Types;
More Code for question in comments:
Chess_Types.Piece_Types.ads:
package Chess_Types.Piece_Types is
type Pawn is new Piece with
record
First_Move : Boolean := True;
end record;
overriding
procedure Move_Piece(Board: in CB_Vector'Class; Po: in Pawn; Move_To: in Board_Index);
-- Other piece types declared here
end Chess_Types.Piece_Types;
Chess_Types.Piece_Types.adb:
with Ada.Text_IO;
use Ada.Text_IO;
package body Chess_Types.Piece_Types is
procedure Move_Piece(Board: in CB_Vector'Class; Po: in Pawn; Move_To: in Board_Index) is
Index_From, Index_To : Board_Index;
Move_From : Board_Index := Po.Coordinate;
begin
-- Obtain locations of Pawn to move from (Index_From) and to (Index_To)
-- in terms of the single dimension vector
for I in Board.First_Index .. Board.Last_Index loop
if Board.Element(I).Coordinate = Move_From then
Index_From := I;
end if;
if Board.Element(I).Coordinate = Move_To then
Index_To := I;
end if;
end loop;
-- Determine if the requested move is legal, and if so, do the move.
-- More possibilties to be entered, very primitive for simple checking.
if Move_To - Move_From = 2 and then Po.First_Move = True then
Board.Swap(I => Index_From, J => Index_To); -- "actual for "Container" must be a variable"
Board.Element(Index_From).First_Move := False; -- "no selector for "First_Move" for type "Piece'Class"
elsif Move_To - Po.Coordinate = 1 then
Board.Swap(Index_From, Index_To); -- "actual for "Container" must be a variable"
end if;
-- Test to make sure we are in the right Move_Piece procedure
Put_Line("1");
end Move_Piece;
-- Other piece type move_piece procedures defined here
end Chess_types.Piece_Types;
As a note to understand further, the Coordinate component of each piece correspond to ICCF numeric notation, which is two digits, so there needs to be some type of conversion between the vector and the ICCF notation, hence the reason for the whole for loop at the start.
This is a tough one. It looks like limited with and generics don't play nice together. The only way to make it work is to go back to using your own access type:
with Ada.Containers.Vectors;
use Ada.Containers;
limited with Chess_Types;
use Chess_Types;
package Chessboard_Package is
subtype Board_Index is Integer range 1 .. 64;
type Piece_Acc is access all Piece'Class;
package Chessboard is new Vectors(Board_Index, Piece_Acc);
end Chessboard_Package;
I had to put the instantiation into a new package, and move the Board_Index there too. Also, I changed it to Vectors since Piece_Acc is a definite type and there's no point in using Indefinite_Vectors. But in any event, this defeats the purpose. I'm just not sure Ada gives you a way to do what you want with two packages like this.
Even doing it in one package is not easy:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is tagged record ... end record;
type CB_Vector is tagged;
procedure Move_Piece (Board : in CB_Vector'Class;
P : in Piece;
Move_To : in Board_Index);
package Chessboard is new Indefinite_Vectors(Board_Index, Piece'Class);
type CB_Vector is new Chessboard.Vector with null record;
end Chess_Types;
This compiles, but I had to add extra stuff to get around some of the language rules (in particular, when you instantiate a generic, that "freezes" all prior tagged types so that you can no longer declare a new primitive operation of the type); also, I had to make the Board parameter a classwide type to avoid running into the rule about primitive operations of multiple tagged types.
As I understand it, this will do what you want.
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is abstract tagged
record
Name : String (1 .. 3) := " ";
Alive : Boolean := False;
Team : Color;
Coordinate : Board_Index;
end record;
type Piece_Ptr is access all Piece'Class;
package Chessboard is new Indefinite_Vectors(Board_Index, Piece_Ptr);
procedure Move_Piece (Board : in Chessboard.Vector;
P : in Piece'Class;
Move_To : in Board_Index) is abstract;
end Chess_Types;
NOTES:
Piece is now abstract, as is the Move_Piece method. This will mean you now need to derive your other piece types (package piece_type-rook.ads, with a move_piece method for rook) etc...
Chessboard now contains pointers (Class wide pointers), beware allocating, deallocating, deep copy, shallow copy issues when using it.
You should now be able to call Move_Piece on any dereference of a piece_ptr and have it dispatch to the correct method.
The Move_To parameter is now the same type as the Board_Index. (Coordinate also brought in line) -- this seems a bit clunky, perhaps rethink this. (Row & Column Indices defining a 2D array perhaps? --No need for Indefinite_Vectors)
To answer the second question in the comment:
To use First_Move, the procedure has to know that it's a Pawn. If the object is declared with type Piece'Class, you can't access components that are defined only for one of the derived types. (That's true in most OO languages.) This may indicate a flaw in your design; if you have a procedure that takes a Piece'Class as a parameter, but you want to do something that makes sense only for a Pawn, then maybe you should add another operation to your Piece that does a default action for most pieces (perhaps it does nothing) and then override it for Pawn. Other possibilities are to use a type conversion:
procedure Something (P : Piece'Class) is ...
if Pawn(P).First_Move then ...
which will raise an exception if P isn't a Pawn. If you want to test first, you can say "if P in Pawn". I sometimes write code like:
if P in Pawn then
declare
P_Pawn : Pawn renames Pawn(P);
begin
if P_Pawn.First_Move then ...
end;
end if;
But defining a new polymorphic operation is preferable. (Note: I haven't tested the above code, hope I didn't make a syntax error somewhere.)
I want to declare an array with element-type of a variant record.
Something like this:
type myStruct (theType : vehicleType) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
myArray : array (Positive range <>) of myStruct;
But in this case I get error.
It only allows this:
myArray : array (1.100) of myStruct(Car); //such as
So how to solve the index-problem and how to describe an array of a variant record's type without giving it's discriminant?
The example above will not compile. Here is a correct version (I changed mystruct to Integer for simplicity):
procedure test_array is
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Arr_T is array (Vehicle_Array_Index range <>) of Integer;
type Vehicle_Array (Size: Vehicle_Array_Index := 1) is record
Vehicles : Arr_T(1..Size);
end record;
begin
null;
end;
One of the errors was that you cannot have anonymous arrays inside records, and second, you should use the discriminant to constrain the array inside.
As mentioned above this is not a good solution for varying-length arrays as most likely you will get a maximum sized array anyway. If you want arrays with dynamically determined sizes you can use blocks for that.
declare
a: array(1..n) of integer; -- n can be a variable here
begin
--some code using a
end;
It also works in the declaration parts of procedures and functions where n can be a parameter passed to the subprogram (one of the advantages Ada has over C/C++). And of course you can just allocate arrays dynamically on the heap using allocators.
If you want to be able to create objects of a discriminated type and change (or figure out) what the type is at runtime, you have to give the discriminant a default value in the declaration.
The reason for this is that Ada doesn't want to have to worry about dealing with uninitialized discriminated objects that it can't even figure out the size and valid record fields of.
For this reason, and some reasons I go into a bit in the comments, Ada discriminated records aren't actually very useful in mixed language programming (eg: exactly duplicating a C union). They can be handy on their own terms though.
For the example you give, you'd do the following (warning: Not compiled):
type myStruct (theType : vehicleType := car) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
Then you could set one of the array values at runtime thusly:
myArray(20) := (theType => car,
field1 => myArray(20).field1,
... , --// Fill in the rest of the fields by name
numberOfWheels => 4);
As for this part:
myArray : array (Positive range <>) of myStruct;
You cannot declare actual array objects with an indeterminate range like this. You could declare a type that way, but an object has to have an actual size. If you want an array of varying length, you can once again use a variant record. (Again, not compiled)
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Vehicle_Array (Vehicle_Array_Index : Size := 1) is record
Vehicles : array (Vehicle_Array_Index range <>) of myStruct;
end record;
Oh, and one more thing. You aren't doing this in your example, but if you ever want to use your discriminant to size an array like above, beware. When you declare objects of that type (again, assuming you used a default for the discriminant), the compiler will try to reserve enough space for the largest possible size you could ever feed it a value for. That makes it a Very Bad Idea to make a discriminated array indexed by something with a huge range like Integer or Positive. I know computers are bigger these days, but still most folks don't have 4 gig to spare for one silly little array. So if you are using your discriminant as an array index, it would be a good idea to create a smaller subtype of Integer to use for the type of the discriminant.