Gentle introduction to HAHA languageΒΆ
Suppose that we want to write a function that given a number n returns the sum of all subsequent numbers from 1 to n inclusively. We start with a header of the function:
function sum( n : Z ) : Z
it contains the keyword function
that tells the system to
interpret the following expressions as a function. Then the name of
the function is given, in this case it is sum. The information about
its parameters is enclosed in the parenteses. In our case, we have one
formal parameter that is called n. We declare the type of the
parameter after the colon. It is Z this time, i.e. the type of
integer numbers as we know them from mathematisc (these are not 32-bit
integer numbers frequently met in programming languages). In the end,
the parentesis with parameters is followed by the declaration of the
result type. In our case this is again Z.
This function header can be followed by the definition of the body. The header with the body together look as follows:
function sum( n : Z ) : Z
begin
x := 1
y := 0
while x <= n do
begin
y := y + x
x := x + 1
end
sum := y
end
The code should not be surprising as we have all seen at least one
implementation of the sum in our lives, especially for those who are
familiar with Pascal as much of the syntax is based upon the language.
However, let us discuss the details of the definiton to feel at ease
in further development of other programs. First of all, the code as it
stands is not syntactically correct. We assume in our language that
all variables that are used in code must be declared
beforehand. Therefore, we need to add between the function header and
body declarations of the variables x
and y
so that the code is
as follows:
function sum( n : Z ) : Z
var x : Z
y : Z
begin
x := 1
y := 0
while x <= n do
begin
y := y + x
x := x + 1
end
sum := y
end
The keyword var
, as in Pascal, marks the beginning of variable
declaration sequence. Unlike Pascal, the elements of the sequence are
not separated with semicolons ;
, but with newlines. This holds
both for variable declarations and instructions.
The variable assignemt is done in the Pascal style as in:
x := 1
or:
y := y + x
We also use the Pascal style to define the return result of the function, i.e.:
sum := y
The while loop is defined by a phrase of the form:
while x <= n do
followed by an instruction the loop iterates over. In our case this is
a block instruction enclosed between begin
and end
, i.e.:
begin
y := y + x
x := x + 1
end
To express our intent with regard of the function we can add a precondition and postcondition formulae. These are located between the function header and its body. This looks as follows in this case:
function sum( n : Z ) : Z
precondition n >= 1
postcondition sum = n * (n+1) / 2
var x : Z
y : Z
begin
...
In this case the precondition, introduced with precondition
keyword, says that the function can be called when the parameter n
is not less than 1. The postcondition, introduced with
postcondition
keyword, says that the result is equal to the
commonly known (oh, our Gauss heritage) closed formula for the sum of
the integers from 1 to n. These conditions can be named to
make future reference easier:
precondition natural: n >= 1
postcondition gauss: sum = n * (n+1) / 2
Hoare logic prescribes that each instruction must be surrounded by two assertions. The first one describes the condition of the program state that is expected before the instruction and the second one describes the state resulting from the execution of the instruction. The precondition-postcondition pair are the assertions for the whole function. The assertions for other instructions are written in curly brackets located between instructions. To save notational burden we do not write the first and last assertions in function as these are expressed with precondition and postcondition respectively. Therefore, the initial assignments decorated with the assertions look as follows:
begin
x := 1
{ n >= 1 /\ x = 1 }
y := 0
{ n >= 1 /\ x = 1 /\ y = 0 }
while x <= n do
The loop invariant condition, i.e. the formula that at the entry point
to the loop at each its iteration, is marked with a special keyword
invariant
. So the while loop header augmented with the invariant
is as follows:
while x <= n do
invariant y = x * (x-1) / 2 /\ x<= n+1 /\ n >= 1
begin
This invariant formula can be named to make future reference more accurate:
invariant gauss: y = x * (x-1) / 2 /\ x<= n+1 /\ n >= 1
Again, the presence of the invariant formula gives us excuse not to mention assertions at the beginning and at the end of loop body as they equal to the invariant. This makes the loop body look as follows:
begin
y := y + x
{ y - x = x * (x-1) / 2 /\ x<= n /\ n >= 1 }
x := x + 1
end
We can now combine all the assertions with the code and obtain the complete example:
function sum( n : Z ) : Z
precondition natural: n >= 1
postcondition gauss: sum = n * (n+1) / 2
var x : Z
y : Z
begin
x := 1
{ n >= 1 /\ x = 1 }
y := 0
{ n >= 1 /\ x = 1 /\ y = 0 }
while x <= n do
invariant gauss: y = x * (x-1) / 2 /\ x<= n+1 /\ n >= 1
begin
y := y + x
{ y - x = x * (x-1) / 2 /\ x<= n /\ n >= 1 }
x := x + 1
end
{ y = n * (n+1) / 2 }
sum := y
end