Most programmers get really frustrated at first when they encounter Ada. It's only fair to say that it can be hard to get used to the type restrictions of the language.
"I have this function that adds 64 to 'a' to make 'A'. Why doesn't it work here in Ada?"
Because, to put it simply... are you completely sure you want to do that? Then tell me so.
There are many questionable things happening in that simple function. Can you Add numbers to letters? Should you even think on those terms? What is your level of abstraction? Is it always going to work like that or is it implementation dependent?
Ada does allow you to do anything, but you have to be very explicit.
To give you a feeling of it, if you are not clear enough, Ada will get nervous and stop working.
C-family languages will find something unclear and just interpret it ("It's clear that the variable Bool_Launch_Nuclear_Missile (maybe named BLNM) of the type Nuclear_Attacks is a integer number and that the function Print_PDF will set it to Armageddon_Protocol! It's so obvious").
Not allowing this is a safety feature. You might think is ludicrous to think this can happen, but Ada is used in control of nuclear reactors, defense systems, and many sensitive systems.
If something unexpected happens, it jumps to the exception control, it doesn't try to work it out.
Trust me, it's better this way in this context.
To keep this safety features Ada doesn't implicitly mix types in a variable. It's strongly typed.
All variables have a type, optimized by the compiler to fit in the smallest memory space possible and work the fastest on run time.
The compiler of Ada is quite complex, compared to C++. But this allows these complexities and abstractions to exist without any sacrifice on performance.
Since the type of a variable is invariant, compilers can ensure that operations on variables are compatible with the properties intended for objects of the type.
Ada's Reference Manual
The integer type, as an example, can be assigned to a variable as any of the following ways.
n: integer := 1_912_596; --Underscore (_) is used as separator
age: integer range 0..120; --Ranged int
big_n: integer:= 3E6; --Exponential notation
bin_n: integer:= 2#101#; --base 2 (can be any base 2..16)
True_n:constant integer :=1; --Constant declaration
A_Month:integer range 1..12;
The "identifier : type" grammar will probably be very foreign to many programmers, but it will be incredibly familiar to Mathematicians.
You can build a subtype or a new type.
The difference is that a subtype can be used in the processes as the parent type, without limitation outside the explicit ones (can be out of a range).
New types cannot. Only the basic algebraic operators are defined, but you cannot mix types together.
subtype Month is Integer range 1 .. 12;
n : Integer := 3;
Jan: constant Month := 1;
My_Birthday : Month := 2;
My_Birthday := Jan; --Legal
My_Birthday := n; --Legal
n := My_Birthday; --Legal
type Apples is new Integer; --New type made from Integer
type Oranges is new Integer range 1..1_000;
type Lemons is range 1..1_000; --Also new integer, shorthand
--Apples and Oranges DON'T mix!!!
--Out of range values will raise (throw) exceptions
type Cake is range 0..(2**8)-1; --On Base 2 to control size occupied.
apple: Apples :=1;
apple_ck: Cake:= cake(apple); --Explicit conversion required
n: Integer := 1;
--apple := apple + n: --Error. Apple is type Apples and n is integer.
apple := apple + apples(n):
type Hour is mod 24; --Modular type. Behaves cyclically.
Note that new types are not aliases. They are new types built in by the compiler (and optimized for the functionalities the processor can predict and manage).
As a complete real number set and continuous arithmetic are quite resource consuming, you can dispose of many types that work mirroring the real number set.
You can use the float types defined by default, or you can define your own "Real Numbers" by precision and range using delta, range and digits. These are newdiscrete types constructed for the occasion.
f: float := 1.912_596; --Float type as in C
pi_f: constant float:= 3.141_59;
a,b,c : long_float; --double
x,y,z : long_long_float: --triple
subtype temperature is float range -100.0 .. +100.0;
--Constrain on Floats
type value is digits 2 range 0.00..1_000_000.00;
--Two decimal digits of accuracy
type Volt is delta 0.125 range 0.0..12.0;
type Money is delta 0.01 digits 15;
--Size of 15 digits, but only two of them decimals
subtype salary is Money digits 10;
--Constrain on Money
You can build a composed type using the codeword record.
type My_Type is record
a: integer;
b: float := 99.7;
end record;
Valued defined types to codify real life data was never simpler to do.
It also elegantly combines with subtypes.
type Days is (Mon,Tue,Wed,Thu,Fri,Sat,Sun);
subtype Workdays is Days range Mon..Fri
with default_value => Mon; --Yes, you can set default values
subtype Weekend is Days range Fri..Sun;
You can apply arbitrary constraints and invariant to a type as part of the contract-based programming features.
The enumeration Ada types are not numeric in nature. They cannot be added or multiplied, as it should be obvious, without defining the functions that do so.
Incredibly usefully, there's an in
operator for determining membership of a type returning a Boolean value.
if (day in (Mon, Wed, Fri) then ... end if;
if (i in 0..5) then ... end if;
if (i in 3 | 5 | 7 | 9..11) then ... end if;
Note that in general variables are not initialized by default. They may contain garbage values.
Except pointers, that initialize to null.
Builders can be easily constructed with a function.
function New_Integer return Integer is 0;
Variables are easily treated in Ada, but they do not contain semantic value on the case system. Ada is not case sensitive.
This forces programmers to make readable names.
with Ada.Text_IO;
with Ada.Integer_Text_IO;
procedure Hello_types is
--Renames--
package T_IO renames Ada.Text_IO;
package I_IO renames Ada.Integer_Text_IO;
type Age_type is Integer range 0 .. 100;
cake: Integer := 314;
begin
T_IO.put("Hello. What's your age again? ");
I_IO.get(Age); --Might cause an exception if out of range
T_IO.put("So you are ");
I_IO.put(Age);
T_IO.put_line(" years old! ");
T_IO.put_line("The cake is a lie:"); --See how Ada is NOT case sensitive
I_IO.put(cake);
I_IO.Put(Cake);
I_IO.PUT(CAKE);
i_Io.PuT(cAkE); --but this is retarded anyway
exception --ALWAYS control your exceptions!
when constraint_error =>
T_IO.Put_Line("Really?");
end Hello_types;
The special operator ' is used to call attributes of a type. These are like predefined methods.
They recall certain information related to the type.
Using the constructed type Days, we can use (without need of defining them) some attributes.
declare
d: Days; n: Integer; s: String;
begin
d:= Days'First;
d:= Days'Succ(d); --Succesive to d, next
d:= Days'Pred(d); --Predecesor to d, previous
d:= Days'Last;
n:= Days'Pos(d); --Returns the position of the value in int
d:= Days'Val(n); --Return the nth value
s:= Days'Image(d); --Returns a string with the value's name
end;
Some attributes also work with "real" types. But not all attributes work for all types.
declare
v: values; n: Integer; s: String;
begin
v:= values'First; --The most negative number
v:= values'Succ(v); --Next possible value
v:= Days'Image(v); --Returns a string with the value's name
end;
The only mandatory types in Standard are Boolean, Integer (and subtypes), Float, Duration, Character, String, Wide_Character, Wide_String, Wide_Wide_Character, Wide_Wide_String, and Wide_Wide_West.
No, ,wait, that last one is a Will Smith movie, right?
My point is that these types are a built in, as in any language, but implementation can vary producing unexpected problems of portability in, say, a limited resource system (a washing machine doesn't need very precise floats).
If you want full portability you have to build your types.
Example given, if you need a 64-bit type
type Int_64 is
range -(2 ** 63) .. +(2**63 -1);
If the compiler can't build it, it will reject the compilation altogether, instead of trying to patch up with the equivalent built in type.
It's worth mentioning that using Integers with strings or exponentiation will not lead to portability issues, unless you are doing some crazy hack.