Ada Programming/Constants
Are Constants Constant?[edit | edit source]
Variables and constants are declared like so:
X, Y: T := Some_Value; -- Initialization optional
C: constant
T := Some_Value;
The initialization value for X and Y is evaluated separately for each one – thus, if this is a function call, they may have different values.
For the nomenclature: Ada calls variables and constants together "objects". Do not confuse this with the term "objects" used in OOP.
It is good practice to declare local objects which do not change as constant.
Silly question: What actually does "constant" mean?
procedure
Swap (X, Y:in out
T)is
Temp:constant
T := X;begin
X := Y; Y := Temp;end
Swap;
In the Swap example, the temporary copy of X certainly is constant – we do not touch it. However (a triviality you’ll say), it is not constant during construction nor during destruction at the end of Swap.
Hold it!
In the following, you will learn some astonishing facts about “constants” (objects with the keyword constant).
First Doubts[edit | edit source]
Let us sow first doubts about the constancy.
Controlled types (RM 7.6) grant the programmer access to the process of construction, copy and destruction via the three operations Initialize, Adjust and Finalize.
Given a controlled type T.
X: constant
T := F;
After construction of X and its initialization with a copy of F, the operation Adjust is called with parameter mode in out for the constant object X. (Initialize is not called here because an initial value is given.)
In Ada.*_IO (RM A.6-A.10), the file parameter of Create and Close has mode in out, of Read and Write (resp. Put and Get) it has in. But each of these operations does change the state of the file parameter.
This reflects a design where the (internal) File object's "state" represents the open-ness of the File object, rather than the content of the (external) file. Agreed that this choice is debatable, but it is consistent across the I/O packages.
The presumed implementation model implies a level of indirection, where the File parameter represents a reference, which is essentially "null" when the file is closed, and non-null when the file is open. Everything else is buried in some mysterious netherland whose state is not reflected in the mode of the File parameter.
This is a symptom of any language that has pointer parameters whose mode has no effect on the ability to update the pointed-to object.
So:
An object is not constant at least during construction and destruction.
But as you have seen with Ada.*_IO, a constant object can indeed change its state also during its lifetime. (An in parameter is like a constant within a subprogram.)
“The constant keyword means that the value never changes” – this is a False statement for Ada (the details are painful); it's true for elementary types and some others, but not for most composite types.
You can find some interesting statements about this in Ada RM: RM 3.3(13/3, 25.1/3), 13.9.1(13/3). The corresponding terms are immutably limited and inherently mutable.
Example Pointers[edit | edit source]
Pointer:declare
type
Recis
record
I:access
Integer;end
record
; Ob:constant
Rec := (I =>new
Integer'(42)); -- Ob.I is constant, but not Ob.I.all!begin
Ob.I.all := 53;end
Pointer;
Here you will certainly agree with me that this is clear as daylight.
But what about the case of an internal pointer accessing the whole object itself?
Reflexive:declare
type
Recis
limited
record
-- immutably limited -- Rosen technique (only possible on immutably limited types); -- Ref is a variable view (Rec is aliased since it is limited). Ref:access
Rec := Rec'Unchecked_Access; I : Integer;end
record
; Ob:constant
Rec := (I => 42, Ref => <>);begin
Ob.I := 53; -- illegal Ob.Ref.I := 53; -- erroneous until Ada 2005, legal for Ada 2012end
Reflexive;
The object must be limited since copying would ruin the reference.
Ada 2012 AARM 13.9.1(14.e/3): The Rosen trick (named after Jean Pierre Rosen) is no longer erroneous when the actual object is constant, but good practice.
This applies to objects where there is necessarily a variable view at some point in the lifetime of the object which you can "squirrel away" for later use. This certainly does not happen "by mistake", the programmer does it on purpose.
package
Controlledis
type
Recis
new
Ada.Finalization.Controlledwith
record
Ref:access
Rec; -- variable view I : Integer;end
record
;overriding
procedure
Initialize (T:in out
Rec);overriding
procedure
Adjust (T:in out
Rec); -- "in out" means they see a variable view of the object -- (even if it's a constant)end
Controlled;
You can use it like so:
declare
function
Createreturn
Controlled.Recis
…end
Create; Ob:constant
Controlled.Rec := Create; -- here, Adjust works on a constant on an assignment operationbegin
Ob.Ref.I := 53; -- Ob.I := 53; illegalend
;
A copy of the return object Create is assigned to Ob, the object Create is finalized. Now the component Ref points to a no longer existing object. (Note the difference between assignment statement and assignment operation.) Adjust has to correct the wrong target.
Create might look like this:
function
Createreturn
Controlled.Recis
X: Controlled.Rec; -- Initialize calledbegin
return
X;end
Create;
The variable X is declared without an initial value, so Initialize is called. Create returns this self referencing object.
This is the package body:
package
body
Controlledis
procedure
Initialize (T:in out
Rec)is
begin
T := (Ada.Finalization.Controlledwith
Ref => T'Unchecked_Access, I => 42);end
Initialize;procedure
Adjust (T:in out
Rec)is
begin
T.Ref := T'Unchecked_Access;end
Adjust;end
Controlled;
Initiallize is only called if no initial value is given in the object declaration. T is regarded as aliased so that the component Ref is a variable view of the object itself.
Example Task and Protected Object[edit | edit source]
Tasks are active objects, i.e. each task has its own thread of control: All tasks run concurrently. A task communicates with other tasks via rendezvous by calling one of its entries (roughly corresponding to a subprogram call).
Since Ada is a block-oriented language, tasks may be defined as components of arrays and records, and these may be constants.
task
type
TTis
entry
Set (I: Integer);end
TT;task
body
TTis
-- local objects are not I: Integer := 42; -- considered part of constantbegin
accept
Set (I: Integer)do
TT.I := I;end
Set;end
TT;type
Recis
record
T: TT; -- active object that changes state (even if "constant")end
record; Ob:constant
Rec := (T => <>);
Ob.T.Set (53); -- execution of an entry of a constant task
Protected objects are very similar to tasks.
protected
type
Protis
procedure
Set (I: Integer);private
I: Integer := 42; -- *end
Prot;protected
body
Protis
procedure
Set (I: Integer)is
begin
Prot.I := I; -- Prot.I is *end
Set;end
Prot;type
Recis
limited
record
Ref:access
Rec := Rec'Unchecked_Access; -- reflexive P : Prot;end
record
; Ob:constant
Rec := (others
=> <>);
Ob.P.Set (53); -- illegal Ob.Ref.P.Set (53); -- variable view required
Constants Are Like This[edit | edit source]
If an object is declared “constant”, the meaning is just twofold:
- The object cannot be used in an assignment (if it is not yet limited).
- The object can only be used as an in parameter.
In no case at all does it mean that the logical state of the object is immutable. In fact, such a type should be private, which means the client has no influence at all on what happens in the object’s inside. The provider is responsible for the correct behavior. Immutably limited and controlled objects are inherently mutable.