Algorithms cp
Algorithms cp
Omari Mohammed
Maître de Conférences Classe A
Université d’Adrar
Courriel : omarinmt@gmail.com
SUPPORT DE COURS
-1-
The Programming Languages
Definition: A programming language is a language intended for the description of programs. Often a
program is expressed in a programming language so that it can be executed on a computer:
A programming language is capable of expressing any computer program. However, not all
languages are equally powerful. Therefore, we will study the theoretical concept of some languages, and
concentrate on the practical power of each. Hence, we will be able to choose among languages the best
that fits our needs in an efficient way.
Language Paradigms: Usually, any language has to belong to one paradigm. The latter describes a
group or a concept that a language basically rely on.
There are four language paradigms:
- Imperative paradigm: Which include languages that are based on the sequential execution of
instructions; e.g., Fortran, Pascal, C, …
- Functional paradigm: In this group, the function is considered as a “first-class-citizen”. All
applications are interpreted into function calls. This paradigm is characterized by the un-necessity
of memory storage. The most common function-oriented language is LISP.
- Object-Oriented Paradigm: Even though the programs are still imperative, object-oriented
languages focus on the classification of data and programs into objects that interact with each
other by sending messages. Languages of this paradigm are widely used nowadays such as Java
and C++.
- Logic-Oriented Paradigm: This paradigm takes a step towards the “non-procedural” aspect. It is
based on “what-to-do” more than “how-to-do”. Prolog is considered as the king of logic-oriented
languages.
-2-
Pseudo-Codes and Interpreters
A language is a tool to implement our ideas on a machine. However, there are
many layers that separate our code from the hardware:
1- Micro Level: where instructions are given to open/close electronic gates.
R1 R2 R3 IR AR ACC
R1in R1out R2in R2out R3in R3out IRin IRout ARin ARout ACCin ACCout
0 0 0 0 0 0 0 1 1 0 0 0
2- Macro Level: or the Assembly level. It was invented as a pseudo code which
provides a set of basic instructions that are easily translated to a machine code (micro-
code).
ASSEMBLER
Assembly Micro-Code
Translating
Example:
- MOV R1, R2 (Store R1 into R2)
- ADD R1, R2 (Add R1 to R2 and store the result in R2)
3- H.L.L Level: High level languages provide more flexible features for
programmers. Simplicity, portability, maintainability and readability are easier
implemented in H.L.L.
COMPILER
H.L.L Assembly
Translating
-3-
4- V.H.L.L Level: Very H.L.Ls are designed as packages of routines to be used
directly by programmers (Java, C++, Simila, …)
PRE-PROCESSOR
V.H.L.L H.L.L
Translating
H/W
Micro-Coding
Assembly-Coding
More Speed More : Readability,
Portability,
HLL Programs Maintainability, …
VHLL Programs
-4-
Virtual Machine Real Machine
Punched Card
holes
INTERPRETER
In order to better understand a pseudo code and be able to interpret it, we will
choose to encode using digits (0 to 9) instead of bits ( 0 and 1).
Let’s imagine a machine with a memory of 1000 words. Each word consists of
11 digits. This allows us to encode pseudo instructions of the following format:
Op Op1 Op2 Dest
Where:
-5-
Op: 2 digits encoding an operation (±0 to ±9).
Op1: 3 digits encoding the address of operand 1 (000 to 999).
Op2: 3 digits encoding the address of operand 2 (000 to 999).
Dest: 3 digits encoding the address of the destination (000 to 999).
.
.
.
999 + 0 0 0 0 0 0 0 0 0 0
+ (0) −(1)
+ −
1
(addition) (subtraction)
2 × ÷
(multiplication) (division)
3 square square root
-6-
Memory (Program Section)
000 + 1 3 2 0 4 5 6 6 5 7 an instruction
001 - 2 5 0 4 3 7 4 7 8 0 Divide the value stored at
location ‘504’ on the value
002 + 3 6 8 7 1 0 0 2 0 0 stored at location ‘374’ and
store the result at location
.
‘780’.
.
.
.
999 + 0 0 0 0 0 0 0 0 0 0
Here is the complete table of our pseudo code which takes care of flow-control
and input output operations:
+ −
0 Move ?
1 + −
2 × ÷
4 = ≠
5 ≥ <
6 X[Y]→Z X→Y[Z]
Increment and
7 ?
test
8 Read Print
9 Stop ?
-7-
III-2 Encoding The MOVE operation:
This operation the simplest one where we need to move a content of one
location to another:
+0 150 000 280
effectively moves the content of location 150 to location 280. As you can notice, the
move operation has two parameters only: source and destination. Therefore, the
second operand is unused (000).
999 + 9 0 0 0 0 0 0 0 0 0
-8-
-5 200 000 035 means that if the content of location 200 is negative then branch
to execute operation in location 035.
Here is an example of a pseudo code that computes the absolute value of the
content of location 270 and stores the result in 305:
Program Section
(loc 000) +5 270 000 002 (if [270] >= 0 goto 002)
(loc 001) -1 000 270 270 (else -[270] → [270])
(loc 002) +0 270 000 305 ([270] → [305])
-9-
Program Section
(loc 000) +6 005 000 003 (A[0] → MIN)
(loc 001) +6 005 001 004 (A[i] → TEMP)
(loc 002) +5 004 003 004 (if TEMP >= MIN goto 004)
(loc 003) +0 004 000 003 (else TEMP → MIN)
(loc 004) +7 001 002 001 (inc i; if i < 5 goto 001)
-10-
V- The Loader
The loader is the program who’s responsible for reading the punched cards
and load initial data values, program instructions into memory. However, input data
are not read into memory until executing a +8 instruction. Therefore, the loader will
load initial data values (variables) into the data section in memory until it reads the
+9999999999 card. Then, it starts reading the program instructions into the program
section, until reading another +9999999999 card. The rest of the input data will be
read datum by datum once a read instruction (+8) is executed.
The Memory
Data Section
+9999999999
Program
program instructions
Section
+8 000 000 305 Before
execution
While
executing
+9999999999
-11-
Here is an example of a complete program that computes the minimum of 5
integers:
(loc 000) +0 000 000 000 (the zero value)
(loc 001) +0 000 000 000 (the index i)
(loc 002) +0 000 000 000 (the upper bound n)
(loc 003) +0 000 000 000 (the minimum)
(loc 004) +0 000 000 000 (temporary location)
(loc 005) +0 000 000 000 (A[0])
(loc 006) +0 000 000 000 (A[1])
(loc 007) +0 000 000 000 (A[2])
(loc 008) +0 000 000 000 (A[3])
(loc 009) +0 000 000 000 (A[4])
+9 999 999 999 Separator
(loc 000) +8 000 000 002 (Read n)
(loc 001) +8 000 000 004 (Read TEMP)
(loc 002) -6 004 005 001 (TEMP → A[i])
(loc 003) +7 001 002 001 (inc i; if i < n goto 001)
(loc 004) +0 000 000 001 (0 → i)
(loc 005) +6 005 001 003 (A[0] → MIN)
(loc 007) +6 005 001 004 (A[i] → TEMP)
(loc 008) +5 004 003 010 (if TEMP >= MIN goto 010)
(loc 009) +0 004 000 003 (else TEMP → MIN)
(loc 010) +7 001 002 007 (inc i; if i < 5 goto 007)
(loc 011) -8 003 000 000 (Print MIN)
(loc 012) +9 000 000 000 (stop)
+9 999 999 999 Separator
+0 000 000 005 input data
+0 000 000 073 input data
+0 000 000 018 input data
+0 000 001 056 input data
-0 007 060 080 input data
+0 000 120 034 input data
-12-
VI- The Interpreter
When initial data values and program instructions are loaded into memory, the
interpreter should start an execution cycle. But first, we need to know the data
structures we are using:
/* main data structures*/
char[1000][11] Program;
char[1000][11] Data;
/* additional data structures*/
char[11] Instruction;
char[2] Operation;
int IP, Operand1, Operand2, Destination;
The interpreter starts executing program instructions following the IP value
(Instruction pointer). In the beginning, IP = 0 (or location 000). The execution cycle
of the interpreter looks like the following:
1- Read the next instruction (Instruction = Program[IP]).
2- Increment the IP (IP = IP + 1).
3- Decode the Instruction (Operation = ...; Operand1 = ...).
4- Execute the Instruction (if (Operation == ‘+0’) {...).
5- Go to Step 1.
As we can see, the IP value is incremented automatically at step 2. Yet, it is still
possible to branch to another instruction location rather than the next one if the
current instruction is a flow-control instruction. In this case, at execution step (3), the
IP value is altered based on the result of the comparison:
if (Operation == ‘+4’) { /* if operation is EQUALITY*/
if (strcmp(Data[Operand1], Data[Operand2]) == 0)
IP = Destination;
}
Last thing to say about the interpreter that it should break the execution cycle
when the STOP instruction is read:
if (Operation == ‘+9’) { /* if operation is STOP*/
exit(); /* quit*/
}
-13-
if (Operation == ‘-8’) { /* if operation is Print*/
printf("%s", Data[Operand1]);
}
-14-
1st Generation Languages:
Emphasis on Efficiency
(FORTRAN)
1-Overview:
Interpreters came to execute virtual codes that took care of the lacking
operations of floating point operations and indexing facilities. When IBM developed
its IBM 704 machine with built-in floating point operations, it made current
interpreters look less attractive due to the overhead of simulating such facilities. In
addition, programming with decimal numbers was opposed by many programmers
and the sympathy to use high level language was in the rise.
Punch cards:
Even though the IBM 704 used tapes to store data, punch cards were also
widely used as primary inputs for data and programs. Therefore, FORTRAN inherited
-15-
the fixed format of pseudo codes, and programmers used a specific code to write their
FORTRAN programs. The IBM 80-column card had 12 rows (0-9, 11 and 12), and 80
columns divided as follows:
Column Purpose
1-5 Statement Number
6 Continuation
7-72 Fortran Statement
73-80 Identification
So each statement was coded in one card or more. Each character had a
specific punching code. For instance, Z was coded 0-9, which means the 0th and the
9th row is to be punched to encode a Z. 1 was simply coded 1, 2 for 2 and so forth.
The previous figure shows an encoding for the statement Z(1) = Y + W(1), with
the identification of PROJ039. The identification was only used by the programmer; it
was ignored by the FORTRAN compiler/interpreter.
When the statement needed more than 66 characters (7 to 72), the next card
was to be punched at one hole of the 6th column for continuation. Also, if column 1
encodes the character C, the current statement is considered as a comment.
-16-
2- Data Structure:
In the early versions of FORTRAN, only 4 scalar types were used: Integer,
Float, Double, and Complex.
INTEGER I
FLOAT X
DOUBLE D
COMPLEX C
Since FORTRAN was designed to program scientific operations, these types
were first-class citizen objects; hence, adding two complex numbers was simply
handled by a simple plus (+) rather than a special add-function.
FORTRAN aims big at achieving efficiency even in memory usage. Most
computers of that era were word oriented (the basic unit of data representation was a
word). For instance, integer and float variables used 1 word, double and complex used
2 words:
The only data structure that FORTRAN used was the array of 1, 2, and 3
dimensions:
DIMENSION A(10), MATRIX(10, 10), SPACE(10, 10, 10)
The unit used in DIMENSION declaration is word only. Therefore, arrays
could be used for integers or floats only.
-17-
3- Program Structure:
FORTRAN was first to introduce subprogram facilities. The overall
programming structure looked as follows:
Main Program
Subprogram 1
.
.
.
Subprogram n
-18-
3-1 Declarative and Imperative Constructs:
A FORTRAN program is divided into two sections:
- Declarative constructs, which describes variables, their length, and their initial
values. This section is non executable; it is used by the compiler to allocate and set
memory location only (INTEGER I).
- Imperative constructs (instructions), which contained the commands to be executed
by the CPU, which make it an executable section (I = I + 1).
Memory
000
001 X
002
003
.
.
.
FORTRAN also affords an efficient way to initialize data using the DATA
construct. For instance,
DIMENSION A(900)
DATA A / 900*0.0
Initializes, in one shot, 900 location floats, which is more efficient than initializing
them using a loop:
DO 10 I = 1, 900
A(I) = 0.0
10 CONTINUE
-19-
3-3 Conditions, Conditional and Unconditional Branches:
FORTRAN was originally designed as a programming language for the IBM
704 computer; Backus never thought that it would be used for other machines.
Therefore, some FORTRAN’s instructions were designed similarly as the assembly
language of the IBM 704.
STOP HLT
-20-
3-4 GOTO: The Workhorse of Control Flow:
The GOTO statement is a fundamental control structure in FORTRAN.
However, its use caused many implications in terms of readability, consistency and
structure. Nevertheless, we can not escape from the fact that we need a tool to control
the execution flow. Since FORTRAN emphasized on efficiency, the use of the weakly
structured GOTO generated a big push toward efficient coding.
-21-
Here is a third example that handles loops:
c Read 100 elements
DIMENSION A(100)
I = 1
10 READ 20, A(I)
I = I + 1
IF (I .LE. 100) GOTO 10
20 FORMAT(F10.5)
70 STOP
So, it is clearly seen that FORTRAN has one static structure (the GOTO) with
is related to three dynamic structures: the alternative, the case-switch, and the loop. In
other words, it is difficult to correlate the meaning of the program execution (dynamic
behavior) with the program code (static behavior). Therefore, FORTRAN disregarded
another principle of programming languages:
-22-
As we can see, the programmer can easily get confused between the two, simply
because they look alike. This is a violation of the Syntactic Consistency Principle:
The Syntactic Consistency Principle:
Things that look similar should be similar and things that look different should
be different.
-23-
However, this weak typing language made it insecure for use, especially by amateur
programmers. For instance, the square operation that is defined on integers has no
meaning when it comes to strings and addresses. This made FORTRAN violates
another language principle:
The Security Principle:
No program that violates the definition of the language, or its own intended
structure, should escape detection.
Contrary to the low level loop of GOTO, the DO loop is more abstract: the
programmer provides only the necessary ingredients: the counter, the upper limit, the
body of the loop, and the delimiter. In this way, we ask the programmer WHAT TO
DO (less errors), not HOW TO DO it (more errors) like the GOTO construct. Thus
the DO loop illustrates:
The Impossible-Error Principle:
Making errors impossible to commit is preferable to detecting them after their
commission.
-24-
3-9 Subprograms:
Subprograms visualize a very important feature in any language: Abstraction.
Subprograms save a lot of writing, and make it easier to read and maintain. Here is an
example of a subprogram definition and call:
FLOAT A, B, C
READ 10, A
READ 10, B
CALL MIN(A, B, C)
PRINT 20, C
STOP
10 FORMAT(F10.3)
20 FORMAT(7H MIN = , F10.3)
C Procedure MIN
SUBROUTINE MIN(X, Y, Z)
IF (X .GT. Y) GOTO 30
Z = X
GOTO 40
30 Z = Y
40 RETURN
END
The Abstraction Principle:
Avoid requiring something to be stated more than once; factor out the
recurring pattern.
3-10 Activation Records:
Once a caller (main program for instance) calls a callee (subprogram), the
state of the caller might be saved. The state represents all the parameters, the CPU
registers values …
MAIN PROGRAM MIN SUBPROGRAM
IP=0 ________ IP=0 ________
IP=1 ________ IP=1 ________
IP=2 CALL MIN(A, B, C) IP=2 ________
IP=3 ________ IP=3 ________
.. .. .. IP=4 RETURN
-25-
For that matter, a specific place in memory called activation record is reserved for
each main or subprogram:
DL
How is the execution of the caller resumed when the callee is terminated?
Well, we simply assign the saved value of IP (in the caller’s activation record) in the
H/W IP (CPU).
In non-recursive languages as FORTRAN, an activation record is reserved for
each subprogram, whether it is called or not.
3-11 Pass by Reference:
One of the advantages of pass by reference is that it is always efficient; a very good
reason for FORTRAN to embrace it. No matter what is the size of the parameter to be
passed, only its reference or address (few bytes) is transmitted to the callee. The gain
in efficiency can easily be seen when an array is passed as a parameter. However,
pass by reference considers that the parameter to be passed is an input/output
parameter: we read its value in the beginning and store any modification in it
immediately.
So what will happen when we don’t want to update a passed parameter? Simply we
use another copy to pass it. But this solution does not work quiet right with constants.
FORTRAN reserves a memory location for each constant like variables. Once a
constant is passed by reference, it is possible that its value is also modified:
SUROUTINE SWITCH(X, Y)
Z = X
X = Y
Y = Z
-26-
RETURN
END
What happens is we make a call as follows: SWITCH(1, 2)? The compiler
won’t reject it since it’s legal. However, the value of these constants will be altered!!!
SWITCH(1, 2)
R
S
Main Program
X
Call R(X)
Call S(2)
Subprogram R Subprogram S
N N
X Y
Y
Call S(N) Call R(Y)
Would you guess why variables are local in scope? It’s definitely Efficiency.
But how can subprograms share data then? It’s clear that passing variables as
parameters in subprograms call would do so, but that wasn’t enough.
-27-
FORTRAN introduced another construct for data sharing: the COMMON
construct. For example, a subprogram R declares the following common block:
COMMON /C1/ A(100), B(200)
Where S declares:
COMMON /C1/ C(100), D(200)
Which means that a common block C1 of 300 words is declared to be global,
shared by both subprograms R and S.
R
S
Main Program
X
Call R(X)
Call S(2)
Subprogram R Subprogram S
N N
X Y
Y
COMMON /C1/ A(100), COMMON /C1/ C(100),
B(200) D(200)
CALL S(N) CALL R(Y)
-28-
FLOAT A, B, C, DELTA, X
EQUIVALENCE C, X
C Section where C is needed, but not X
DELTA = B**2 – 4*A*C
C Section where X is needed, but not C
IF (DELTA .EQ. 0) X = -B/A
In short, C and X are using the same memory place; or the same memory area
have two aliases or two names to access with: the alias C and the alias X.
However, the EQUIVALENCE structure is outlived: its basic need was for
saving memory, which is not a very big concern nowadays. In addition, it still not
secure to use it in large programs, since the user (only source of error!) has to decide
whether one variable is not to be used after the second one is operational.
4. Syntactic Structure:
Any language is defined by its lexics, syntax and its semantics. Usually the
syntax is defined by a grammar:
<DO-LOOP> ::= DO <LABEL> <VAR> = <EXPR>, <EXPR> [, <EXPR>]
<LABEL> ::= <DIGIT> | <DIGIT> <LABEL>
<DIGIT> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
The lexical analyzer, or the scanner, verifies that a program symbols or
characters belongs to the language, and then generates the useful words or tokens of
the program. The syntactic analyzer, or the parser, verifies that a program respects a
language’s grammar.
-29-
However, FORTRAN precautions didn’t cover all the loopholes. Let’s see the
following example:
DO 20 I = 1. 100
which remarkably looks like the DO statement:
DO 20 I = 1 , 100
Logically, the compiler should catch this error since it’s not a legal DO statement.
Yet, due the adopted implicit declaration, the statement, after ignoring blanks, is
interpreted as an assignment of 1.100 to the real variable DO20I:
DO20I=1.100
So, this error was not caught because of two major flaws: ignoring blanks and implicit
declaration. Had FORTRAN not adopting one of them, the error would’ve been easily
detected. This made FORTRAN missed another principle:
The Defense in Depth Principle:
Have a series of defenses (or defense lines) so that if an error is not caught by
one, it will probably be caught be another.
-30-
PROS:
- First high-level language.
- Algebraic notation was a part of the language.
-Efficiency.
CONS:
- Very static.
- Restricted format.
- Use of confusing GOTO.
- Weak typing.
- No global variables.
- Insecure imposed pass by reference.
- Insecure aliasing with EQUIVALENCE and COMMON constructs.
- NO RECURSION.
- Insecure…
- …
-31-
2nd Generation Languages:
Elegancy and Power
(Algol-60)
1-Overview:
ALGOL (Algorithmic Language) was designed to be a universal language.
This language was meant to be machine-independent; though it should satisfy the
condition that it should mechanically (or automatically) be transferable to machine
programs.
Algol
Program
(Independant)
Algol
Compiler
(dependant)
Machine
Assembly
(dependant)
-32-
2-Data and Name Structure:
2- The 0-1-∞
∞ Principle:
Recall that early versions of FORTRAN allowed 3 dimensions only for arrays.
Also, identifiers were limited to 6 characters, and at most 19 continuation cards were
allowed. As you can see, the programmer has always to remember these numbers
which add another burden besides programming.
On the contrary, Algol-60 prefers to avoid imposing any numbers in the
language, which made Algol-60 the first embracer of:
The Zero-One-Infinity Principle:
The only reasonable numbers in a programming language design are zero, one,
and infinity.
3- Generalized Arrays:
Following the 0-1-∞ principle, Algol-60 made it easier to define arrays of any
number of dimensions. Moreover, indices were of free bounds:
Integer array angle [-180: 180]
Algol-60 adopted another approach in declaring arrays: dynamic arrays. Since
it is not unusual to ignore the size of an array at compile-time, it is possible to decide
it at run-time. Here is an example of computing the average of N absolute values:
-33-
begin
integer N;
sum := 0;
for i := 1 step 1 until N do
begin
real val;
As we can see, the previous program is structured into blocks: the variables of
each one are reserved when its execution is reached. Therefore, unlike FORTRAN,
memory reservation of variables is dynamic in Algol-60.
Dynamic memory reservation leads automatically to dynamic name-binding: a
variable name is not bound to a memory location until the creation of its defining
environment at run-time.
-34-
3- The Block:
Algol-60 adopted another powerful feature: nested environment. For that
matter, it allowed users to use blocks which are similar to compound statement, yet
only blocks that can have declaration section:
A Compound Statement A Block
Begin Begin
statements; declarations;
end; statements;
end;
A compound statement is used to group a set of instructions inside the usual
control structures: if-then-else statement, switch-statement and for-loop.
An Algol-60 programmer can use blocks to define environment of use of some
variables: if a variable is needed only in small section of the program, we might
explicitly define it inside a block that encounter the using section.
N Block A
Data Block B
sum
avg
i
val Block C
-35-
For instance, the scope of the variable val is only the block C, which means
that val is local to the block C. However, the scope of variable N is all three blocks
A, B, and C, which means that N is global: can be used everywhere in the program.
BLOCK A
N BLOCK B
Data
sum
avg
i
SL=NULL
DL
SL BLOCK C
DL val
SL
DL
-36-
5- Static Scoping vs. Dynamic Scoping:
In all languages, the interpretation of a statement or an expression is
determined by the context of that statement or expression. The context or the
environment is decided based on the rules of scopes adopted by the language.
In statically scoped languages, the scope of names is determined statically by
the structure of the program; which means that the interpretation is fixed. On the
contrary, in dynamically scoped languages, the scope of names is determined
dynamically at run-time.
Let’s investigate both static and dynamic scoping of the following program:
Begin
real a, b, c;
.
procedure Add; . Add’s
. Env.
begin SL
c := a + b; DL
end; a 1
procedure P; b 2
integer a, b, c; c 0 P’s
. Env.
begin .
.
a := 1; SL
b := 2; DL
a 3.0
c := 0;
b 4.0
Add;
c 0.0
end;
Add <ipAdd, epAdd>
a := 3.0; Main’s
P <ipP, epP>
. Env.
b := 4.0; .
c := 0.0; .
SL
P;
DL
Add;
end. Stack of Activation Records
So which variables will be used inside the Add procedure? In other words,
which c will be used in that construct, is it that of the main program, or that of the
P procedure?
-37-
In order to answer such a question, we need to know whether the language is
statically or dynamically scoped.
If the language is statically scoped, then the interpretation of variable names is
done statically, following the static link chain: we search first in the local
environment, then in its definer’s, and so on. Since the variables a, b, and c are not
declared in the Add procedure, we should check in its definer’s environment, i.e., the
main program. Therefore, c (of the main program) receives the value of 7.
On the other hand, when the language is dynamically scoped, the
interpretation of variable names is done dynamically, following the dynamic link
chain: we search first in the local environment, then in its caller’s, and so on. Since
the variables a, b, and c are not declared in the Add procedure, we should check in
its caller’s environment, i.e., the P procedure. Therefore, c (of the P procedure)
receives the value of 3.
You might have noticed that even the defined procedures have some
information in the stack of activation records: the closure <ip, ep>. The closure
serves as the value of a procedure like the variable’s value. The ip is the instruction
pointer that point on the address of the first instruction of the procedure. The ep is the
environment pointer that point on the activation record of the definer of the procedure.
Pros Cons
-Static type checking. -Less powerful.
Static -Static behavior reflects dynamic
Scoping behavior (The structure principle).
-Dynamic type checking: less
Dynamic -Power of generecity = Power. efficient, insecure.
Scoping -Static behavior is different than
dynamic behavior.
-38-
3-Control and Syntactic Structures:
3-1 The Alternative Construct : IF
Algol-60 enhanced FORTRAN’s IF structure by adding an alternative ELSE:
if condition then statement else statement;
this construct let the programmer able to avoid the use of GOTOs and labels,
and make the programming more structured.
However, the syntactic convention of Algol did have some problems. Consider
the following example:
if x = 1 then if y = 1 then z := 0 else z := 1;
it is still ambiguous whether the else corresponds to the first or the second if. This
problem is called the dangling else problem. Algol decides to reject the previous
statement and make it illegal; it imposes the use of a compound statement to alleviate
the ambiguity. Therefore, if the else can be associated with the first if as follows:
if x = 1 then begin if y = 1 then z := 0; end else z :=1;
or the second if as follows:
if x = 1 then begin if y = 1 then z := 0 else z :=1; end;
Other languages solved the dangling else problem by associating else with
nearest if.
Another version of IF was invented by Algol: the conditional expression:
absx := if x < 0 then –x else x;
3-2 The Loop Construct : FOR
The FOR construct was very powerful: the loop’s control variable gets its values from
the for-list-elements. These elements can be specified as follows:
1- explicit: e.g., 1.
2- expression: e.g., if x =1 then 2 else -2, i + j*k.
3- step-until format: 1 step 2 until 9.
4- while format: I + 1 while I < 10
For example, the for loop:
for i := 3, 7,
11 step 1 until 16,
i/2 while i>= 1,
2 step I until 32
do print (i)
-39-
will print the sequence: 3, 7, 11, 12, 13, 14, 15, 16, 8, 4, 2, 1, 2, 4, 8, 16, 32.
So basically, the for loop is no more a definite loop where a control variable is
ranging between a minimum and a maximum bound. Then, the for-list-elements are
reevaluated after each iteration (e.g., i/2) to check whether the loop condition is still
satisfied. Hence, when the programmer writes a simple for loop (for i := 1
step 1 until 10 do…), the reevaluation is still performed after each iteration
though it is not needed, which wastes time. Therefore Algol violated an important
language principle:
The Localized Cost Principle:
Users should pay only for what they use; avoid distributed costs.
3-3 Procedures:
Algol used the term procedure for both as what we know now as procedures
and functions; an untyped procedure is a subroutine that doesn’t return a value; a
typed procedure is a function. Also, the procedure is divided into a declaration section
a body:
procedure abs(x, absx) real procedure abs(x)
real x, absx; real x;
value x; value x;
begin begin
if x < 0 then if x < 0 then
absx := –x abs := –x
else else
absx := x; abs := x;
end; end;
Unlike Pascal, Algol didn’t impose the use of a compound statement for the
body of a procedure:
procedure abs(x, absx)
real x, absx;
value x;
absx := if x < 0 then –x else x;
-40-
As we’ve mentioned above, a procedure’s activation record is created only
once the procedure is called, and due this dynamic feature, the activation record is to
be removed once the procedure is terminated:
B B B
A A A A A A A
3-4 Nesting:
The nesting feature adopted by Algol made it possible to declare one
procedure inside another or one block inside another. It enhanced also the structure of
usual constructs as the IF and the loop constructs:
FORTRAN ALGOL-60
IF (.NOT. condition) GOTO 100 if condition then
statement 1 begin
. statement 1;
. .
statement m .
GOTO 200 statement m;
100 statement 1 end
. else
. begin
statement n statement 1;
200 ... .
.
statement n;
end;
-41-
It is clearly seen that there are two blocks nested within the then-body and the
else-body. Hence, Now you can easily imagine a more complicated structure: a block
of IF nested within a block of FOR which is nested within another block of IF, …
FORTRAN ALGOL-60
if condition1 then
for i := 1 step 1 until 10 do
?
if condition2 then
begin
statement 1;
.
.
good luck! statement n;
end
else
begin
statement 1;
.
.
statement n;
end;
-42-
3-6 Pass by Name:
Algol-60 adopted two mechanisms for parameter passing: by value and by
name. Pass by value is safe; the passed variable is not altered. However, it is too
expensive when large data structures are to be passed (e.g., arrays).
Unlike FORTRAN, Algol escaped using pass by reference and adopted
another mechanism that is more powerful: pass by name.
In pass by reference, the address of the actual parameter is calculated once and only
once at the beginning of the procedure and bound for good to the formal parameter:
begin
real a;
procedure P(x);
real x;
begin
print(x); (* Pass by Ref: prints 1*)
a := 2;
print(x); (* Pass by Ref: prints 2*)
end;
a := 1;
P(a);
end;
-43-
Now, let’s consider this second program:
begin
real A[1..10];
integer i;
procedure P(x);
x: real; (* Pass by Ref: x is bound to A[1]*)
begin
print(x); (* Pass by Ref: prints 1*)
i := 2;
print(x); (* Pass by Ref: prints 1*)
end;
A[1] := 1;
A[2] := 2;
i := 1;
P(A[i]);
end;
However, with pass by name, the formal parameter is always reevaluated or
reinterpreted whenever the need to use arises:
begin
real A[1..10];
integer i;
procedure P(x);
x: real; (* Pass by name: no binding here*)
begin
print(x);(* Pass by Name: reevaluate x = A[i]
x is bound to A[1], prints 1
i := 2;
print(x);(* Pass by Name: reevaluate x = A[i]
x is bound to A[2], prints 2
end;
A[1] := 1; A[2] := 2;
i := 1;
P(A[i]); (* P(a + b + c + d) *)
end;
-44-
Pass by name is very powerful: it makes procedure calls very generic. Here is
the famous Jensen’s device example:
n
x = ∑Vi
i =1
begin
real x, V[1..N];
integer i;
real procedure Sum(counter, start, finish, element);
value start, finish;
integer counter, start, finish;
real element;
begin
real S;
S := 0;
for counter := start step 1 until finish do
S := S + element;
Sum := S;
end;
x := Sum(i, 1, n, V[i]);
end.
-45-
However, pass by name is expensive and dangerous: if the programmer wants
to pass a simple variable by reference, he must pass it by name where the variable is
reevaluated needlessly. Also, the swap operation, which needs pass by reference,
doesn’t work in some cases like SWAP(i, A[i]):
-46-
4-Pros:
- Structure facilities: Compound statements, blocks, control structures (IF, FOR,
SWITCH, …).
- Nesting environment (blocks, procedures).
- Stack model of computation.
- Power: recursion, pass by name.
- Efficiency: Dynamic Allocation.
- Stronger typing system.
- Free format.
- 0-1-∞ principle.
- Introducing BNF to describe languages.
- Global scoping: data sharing.
4-Cons:
- No Input/Output primitives.
- Limited typing system.
- No data structure capabilities.
- Dangerous pass by name mechanism.
- Expensive constructs: FOR, SWITCH, pass by name.
- Global variables violate information hiding.
-47-
Third Generation Class of Languages :
Return to Simplicity (PASCAL and MODULA2)
2- Data Structures
2-1 The Primitive Types:
PASCAL inherited the primitive types of data from Algol: integer, real and Booleans.
It has also extended its primitive types by providing characters data type, char. Strings were
implemented as an array of characters.
2-2 Enumerated types:
Although primitive types were handled in PASCAL, it was necessary to provide a
mechanism to define non-numeric types, such as days, months, sex, ....
Consider the following Algol example that handles days operations as well as gender
operations:
-48-
begin
integer sat, sun, mon, tue, wed, thu, fri;
integer today, tomorrow;
integer male, female;
integer gender;
male := 0;
female := 1;
gender := male
end
this example shows the possibility to handle non-numeric data. Yet, these data are not
typed separately; they are handled as integers, and the compiler cannot detect errors that are
related to these types of data:
today := 25;
sun := (mon + tue)/wed ;
gender := tomorrow;
In order to eliminate this lack of security, PASCAL provided enumeration types: types all
constructed via an enumeration or a list of values:
type
DayOfWeek = (Sat, Sun, Mon, Tue, Wed, Thu, Fri);
Sex = (Male, Female);
var
today, tomorrow, yesterday: DayOfWeek;
gender : Sex;
begin
today := Tue;
tomorrow := succ(today); yesterday := pred(today);
gender := Male;
end.
-49-
An abstract data type is defined as the set of values that variables, of that type, can take; and a
set of operations allowed on such type. Enumerated types are abstract data types; hence the
following error:
gender := Tue;
can easily be detected since the value Tue does not belong to the set of values corresponding
to Sex. Moreover, operations like :=, succ, pred, =, >, <, >=, <= are also
allowed and interpreted following the order of the values that appear in the type declaration:
Tue > Sat (returns true)
Tue = Wed (returns false)
succ(Tue) (returns Wed)
succ(Fri) (error at compile-time)
tomorrow := today + 1 (error at compile-time)
Enumerated types are also efficient in memory. In the previous example, gender is a
type of two values. Hence, a bit is enough to represent all values of gender type:
Male Female
0 1
Also, only 3 bits are needed to represent the DayOfWeek type values:
Sat Sun Mon Tue Wed Thu Fri
000 001 010 011 100 101 110
In general, a type of m values needs (┌lg2m┐) bits to represent one of its variables.
2-3 Subranges:
In parallel with the need of non-numeric data, there was also a need to limit an
existing type, or defining a subrange (subtype). For instance, days of the months are integer
values that ranges between 1 and 31. It is clearly possible to declare them as integers, yet the
compiler cannot detect illegal out-of-range assignment such as:
Day := 34;
Subranges allow the programmer to limit the values of an existing type, but they
inherit all the properties of their parent type:
-50-
type
DayOfMonth = 1..31;
var
Day1, Day2, Day3,Day4 : DayOfMonth;
begin
Day1 := 7; (*valid*)
Day2 := succ(Day1); (*valid*)
Day3 := 50; (*not valid*)
Day4 := Day1 * Day2; (*valid*)
end.
Subrange types are also efficient in term of memory use. For instance, the compiler
reserves 5 bits only in order to represent a DayOfMonth variable.
2-4 Sets:
Pascal provides the ability to manipulate sets following sets theory. A set type is
described based on an enumerated type, a subrange, … where the size of the set is finite and
determined:
var
S, T: set of 1..10;
begin
S := [1, 2, 3, 5, 7];
T := [1..6];
if S <= T then write(‘S is included in T’); (* inclusion*)
if 3 in S then write(‘3 belongs to S’); (* belonging*)
S := S * T; (* Intersection*)
T := S + T; (* Union*)
end
-51-
Other set operations are also provided: the difference (-), the equality (=), the non-
equality (<>). However, inexplicably, the pure inclusion (<) and the pure super-set(>) are not
provided.
Sets are also implemented efficiently in Pascal. Each set is assigned a bit string, where
a bit matches an element of the set base type; 0 means that the corresponding element doesn’t
belong to the set; 1 means that it belongs:
S = 1 1 1 0 1 0 1 0 0 0 (*{1, 2, 3, 5, 7}*)
T = 1 1 1 1 1 1 0 0 0 0 (*{1, 2, 3, 4, 5, 6}*)
S∪T = 1 1 1 1 1 1 1 0 0 0 (*{1, 2, 3, 4, 5, 6, 7}*)
S∩T = 1 1 1 0 1 0 0 0 0 0 (*{1, 2, 3, 5}*)
Therefore, a simple bit-by-bit multiplication of the two bit string produces a bit string
corresponding to S∩T. The addition produces a bit string corresponding to S∪T.
In order to test whether an element belongs to a set, a left-shift is performed on the
corresponding string followed by a single test of the current shifted bit:
Does 3 belong to S:
1 1 1 0 1 0 1 0 0 0
← (left shift by 3) 1 0 1 0 1 0 0 0 0 0 0
-52-
Strong typing is a feature where type matching is forced when checking types of actuals and
formals. Pascal considered that the size and the index (dimensions) are part of an array type.
Let’s investigate the following example:
type
vector = array[1..100] of real;
var
U: vector; (*type : vector*)
V : array[1..75] of real; (*type : t1*)
W : array[0..99] of real; (*type : t2*)
Z : array[1..100] of real; (*type : t3*)
S : real;
function Sum(x : vector) : real;
var
i: integer;
begin
for i := 1 to 100 do
sum := sum + x[i];
end;
begin
S := Sum(U); (*legal*)
S := Sum(V); (*illegal*)
S := Sum(W); (*illegal*)
S := Sum(Z); (*illegal*)
end.
So, whenever the dimensions are altered, new functions and procedures have to be
provided; a violation of the Abstraction Principle.
Pascal alleviated this problem by defining a conformant array schema, allowing
subprograms to pass the dimensions as parameters:
-53-
function Sum(x : array[lwb..upb: integer] of real) : real;
var
i: integer;
begin
for i := lwb to upb do
sum := sum + x[i];
end;
-54-
Pascal permits factoring out the prefix used to access a record type using the with-statement:
with P do
begin
x := 1;
Name := ‘Ali’;
Sex := male;
end;
Variant records are also provided using the record constructor. Suppose that we want to build
a structure to hold both teachers and students’ information. There are some information that
are conform only to teachers type like the salary, and some information relevant to students
only like fellowships:
type
Person = Record
Name : String;
Age : 16..100;
Sex: (male, female);
BirthDate: Date;
case kind: (Teacher, Student) of
Teacher:
(Salary: 12000..100000;
HireDate: Date);
Student:
(Fellowship: [180, 240, 270];
RegistrationDate: Date);
end;
Variant records permit the reutilization of space; the same space is used by different type of
structure:
-55-
2-7 Pointers:
Pointers are used as addresses to access a location or a block of locations). They are used to
implement dynamic structures such as linked lists, trees, graphs,…
Pascal imposed the strong typing approach on pointers by declaring them as typed-pointers:
var
p: ^real;
x: real;
c: char;
begin
new(p); (*create a memory allocation and attach it to p*)
p^ := 3.14159;
c := p^; (* illegal*) (* strong typing*)
x := p^; (* legal*)
end
Pointers can also used to access other types such as records. Access to fields is done as
follows:
var
p: ^person;
begin
new(p);
p^.Name := ‘Ali’;
p^.Age := 25;
end
-56-
In a language that adopts the structure-equivalence approach, two objects are
equivalent if and only if their structures are equivalent word by word.
In a language that adopts the name-equivalence approach, two objects are equivalent if
and only if their types have the same explicit name. However, an exception is allowed when
one object’s type is a subrange of another:
var
x: integer;
y: 1..16;
begin
x := y; (* legal*)
y := x; (* legal*)
end
Pros Cons
Name Equivalence -Secure
-Efficient and simple
compilation
Structure Equivalence -Useful in dynamic type-
checking
3- Name Structures
3-1 Constants:
PASCAL includes the ability to name constants of discrete types such as integers, characters,
and enumerated types:
Const
Size = 16;
Letter = ‘a’;
PrayerDay = Fri;
Constants play a big role in maintainability: Let’s say that we’ve wrote a program that
manipulates an array of 16 elements:
-57-
var
A: array[1..16] of real;
sum : real;
I : integer;
begin
…
for i:= 1 to 16 do
sum := sum + A[i];
end
Let’s say that you needed to augment the array size to 20. Then, you’ll need to alter all
the 16s that refers to the size with 20; a job which is proportionally hard.
However, if the size is declared as a constant, only one replacement is needed to shift
from one size to another:
const
SIZE= 16;
var
A: array[1..SIZE] of real;
sum : real;
I : integer;
begin
…
for i:= 1 to 16 do
sum := sum + A[i];
SIZE := 20; (* illegal*)
end
-58-
3-2 Procedures and Functions:
PASCAL implemented sub programs quiet the same as Algol. Yet, Pascal uses explicitly the
keyword function to declare a function:
Function f(x : real): real;
var
i: integer;
G: real;
begin
G := 0;
for i := 1 to 3 do
G := G + pow(x, i);
f := G;
end;
Since Pascal handles recursion too, it adds the keyword forward in order to solve the
declaration problem with one-pass compilers when two procedures (or functions) are calling
each other:
procedure Q(…); forward;
procedure P(…);
begin
…
Q(…);
…
end;
procedure Q(…);
begin
…
P(…);
…
end;
-59-
3-3 Pass by Value and Pass by Reference:
The Revised Report on Pascal described two parameter passing modes: by value and by
reference. The latter replaced the complicated pass by name mechanism of Algol. By default,
a parameter is passed by value (in only); pass by reference is applied when the keyword var
is used.
procedure P(a: integer; var b: integer);
begin
…
end;
Pass by reference is useful when the actual parameter is to be altered (out or in-out). Pass by
reference is also efficient since the address of an object is transmitted rather than the object
itself.
-60-
Using a formal parameter procedure, we should specify its formal parameters and their types
as well in order to aid the compiler do its type checking (strong typing).
4- Control Structures
4-1 Simple Loops: The For Loop:
PASCAL simplified the mysterious Algol’s for loop, and enhance it to look even simpler than
FORTRAN’s:
The loop can step up or down by 1 only. PASCAL has overreacted to the complexity of the
second generation’s for-loop, since non-unit steps are very common. Nevertheless, Pascal for-
loop is more efficient since the loop counter and its incrementing/decrementing can be
handled easily using micro-processor registers.
-61-
5- Pascal’s Contributions:
- Simple and Efficient.
- Char, set, enumerated types, subranges.
- User-type definitions.
- Typed Pointers.
- Record Types.
- Secure Pass by Reference.
- Elegant and simpler Case statement.
- Passing procedures and functions as parameters.
-62-
Fourth Generation Class of Languages :
Modularity and Data Abstraction (ADA)
2- Structural Organization:
Expressions and statements are quiet similar to Pascal and Algol. Yet, Ada enriched the
declaration section with the following classifications:
- Objects
- Types
- Subprograms
- Packages (new data types)
- Tasks (concurrency)
-63-
2-1 Packages:
Packages are modules that are used to hide information and implement abstract data types.
Here is an example of defining a new abstract data type Complex:
package ComplexPackage is
type Complex is private;
i: constant Complex;
function “+” (X, Y: Complex) return Complex;
function “-” (X, Y: Complex) return Complex;
function “*” (X, Y: Complex) return Complex;
function “/” (X: Complex) return Complex;
function Re (X: Complex) return Float;
function Im (X: Complex) return Float;
private
type Complex is
record Re, Im: Float := 0.0; end record;
i: constant Complex := (0.0, 1.0);
end ComplexPackage;
The previous Ada specification code defines a new type using the package facility: It
define a complex type with 4 overloaded operators and two functions. The keyword private
mentions that the section underneath is not accessible by users; it is used only by the compiler
since the latter needs to know the complex variable’s size and the constant’s value(i).
The corresponding Ada body package is to be declared in another module (file) and
compiled separately. The user can use the body’s object code directly with the specification
module. Here is an example of a body package:
-64-
end;
function “*” (X, Y: Complex) return Complex;
begin
return (X.Re*Y.Re – X.Im*Y.Im,
X.Re*Y.Im + X.Im*Y.Re);
end;
function Re(X: Complex) return Float;
begin
return X.Re;
end;
-- other definitions
end ComplexPackage;
The package body, known as the implementor, gives the definition of each name
mentioned in the specification, including local procedures, functions, types that are private;
never used by the user.
The concept of using a specification and a body was first introduced by Parnas in his
design principles:
Parnas’s Principles:
1- One must provide the intended user with all the information needed to use the module
correctly and nothing more (specification).
2- One must provide the implementor with all the information needed to complete the
module and nothing more (body).
-65-
declare
with ComplexPackage; use ComplexPackage;
X,Y: Complex;
Z: Complex := 1.5 + 2.5*i;
begin
X := 2.5 + 3.5*i;
Y := X + Z;
Z := Re(Z) + Im(X)*i;
X := Z.Re + X.Im*i; -- illegal
if X = Y then X := Y + Z;
else X := Y * Z;
end if;
end;
-66-
The Length parameter is initialized at 100 to mention that the size is 100 by default. Let’s
show some of the body of the stack package:
Package body Stack is
ST: array(1..Length) of Integer;
Procedure Push(X: in Integer);
begin
-- ...
End Stack;
Then, it is easy to instantiate statically different stack packages with different sizes:
package Stack1 is new Stack(70);
package Stack2 is new Stack(30);
package Stack3 is new Stack; -- size 100
Here is another example:
generic
PeriodOfStudy is range 3..5;
Package body StudentType is
Id: Integer;
Name: array(1..30) of Character;
Age: Integer;
Sex: (Male, Female);
YearlyAverage: array(1..PeriodOfStudy) of Float;
GeneralAverage: Float;
-- ...
End StudentType;
Then, it will be easy to instantiate these specific types:
package DEUA is new StudentType(3);
package LicenceLMD is new StudentType(3);
package LicenceAcademique is new StudentType(4);
package INGENIEUR is new StudentType(5);
Afterwards, we can easily declare student variables of the previous types:
A, B, C: DEUA;
D,E: LicenceLMD;
F: LicenceAcademique;
G, H, E, J: INGENIEUR;
-67-
Generic packages with parameters are difficult to compile. At compile time, the
specification and the body of each instantiated package is instantiated too; i.e., each stack
package will have its own data section and also its own code like the procedure Push. This
leads to what we call a code explosion at compile time: few line of code are written, yet a
long code is generated at compile time (no code sharing).
3-2 Constants
Ada added the feature of declaring typed constants:
A: constant := 5; -- universal integer type
B: constant Float := 1000.00;
C: constant Integer := f(A); -- function call
LightYear : constant := 5-878-000-000-000;-- use of separators
D: constant := 2#1111-1111# -- binary number
E: constant := 16#FB09# -- hexadecimal number
F: constant := 8#377# -- Octal number
-68-
3-3 Types and Subtypes
Let’s consider the following example:
N: Integer;
type Index is range -100..100;
subtype PositiveIndex is Index range 0..100;
I: Index;
J: PositiveIndex;
Even though ADA embraces name equivalence instead of structure equivalence, yet N, I, J are
of different types, the assignments N := I, and I := J are legal if the range values are respected.
Ada solved this problem by letting the subtype inherit all the operations of its base type. Yet,
a runtime constraint check is performed to verify the range values.
I := N; -- legal; check at runtime
N:= J; -- legal; check at runtime
J := -5; -- legal; check at runtime
-69-
3-5 Index Constraints
Recall that Pascal faced a problem with its strong typing system where the array index was
considered as part of the type. Ada solved this problem with the index constraint feature.
First, an array type with unconstrained index is declared:
type Vector is array(Integer range <>) of Float;
This means that Vector is an array of reals, regardless how many of them. Then, objects of
type Vector are declared by specifying the size only:
A: Vector(1..50);
B: Vector(-100..100);
Now, any procedure that accepts objects of type Vector can receive any object of that type,
regardless its size:
Function sum(V: Vector) return float is
...
for I in V’Range loop
Total := Total + V(I);
end loop
3-5 Arrays
Like Algol-68, Ada adopted the dynamic array facility were the size is not needed at run-time.
type FloatVector is array(1..N) of Float;
At runtime, this declaration is elaborated (not executed); the value of N is to be searched in
order to allocate memory space for the array.
Ada adds nice mechanism to allow contiguous array slices to be assigned and tested for
equality:
A, B: FloatVector;
C: array(1..100) of Float;
D: array(1..100) of Float;
A(1..10) := B(11..20);
If A(30..40) = B(50..60) then...
C(1..10) := D(11..20) -- illegal
-70-
3-6 Hidden Parameters
In Ada, some hidden parameters (type fields) are also used to enhance portability:
DayType is (Sat, Sun, Mon, Tue, Wed, Thu, Fri);
Day: DayType
for Day in DayType’Range loop ...
for Day in DayType’First..DayType’Last loop ...
Natural’First→ 0
Natural’Last→ 65535
Integer’First→ -32768
Integer’Last→ 32767
The next hidden parameters are associated with Integer type:
Integer’Image(65) → ‘A’
Integer’Value(‘A’) → 65
declare
Head: NodePtr := new Node;
Tail: NodePtr := Head;
N1: NodePtr;
begin
for I in 1..10 loop
Tail.Next := new Node;
Tail := Tail.Next;
Tail.Info := I;
end loop;
-71-
N1 := Head; -- Aliasing : N1 and Head are occupying
-- the same memory space
N1.all := Head.all -- a new node N1 is created with all
-- head’s information
end;
4- Control Structures:
Ada provides a richer set of control structures than Pascal’s structures:
- Conditional
- Iterations
- Case statement
- Subprograms
- Goto statement
- Exception Handling (Extra)
- Concurrency (Extra)
4-1 Loops
Ada provides one iteration statement which handles definite, indefinite and infinite iterations
-- indefinite iteration
while A <> B
loop
if A < B then
B := B – A;
else
A := A – B;
end if;
end loop
-- definite iteration
for I in 1..10 -- the loop counter I is defined
loop -- automatically
A(I) := I;
end loop
-72-
-- infinite iteration
I: Integer;
loop
exit when A(I) = 0;
I := I + 1;
end loop
-- infinite iteration
I: Integer;
loop
if A(I) = 0 then
exit;
I := I + 1
end loop
-73-
if Full then raise Stack_Error;
else
begin
Size := Size + 1;
ST[Size] := X;
end;
function Full return Boolean is
begin
return (Size = 100);
end;
end Stack;
Now, suppose that the stack package is used by an application that should handle the situation
where the stack is full? How? Well it depends on the conceptor: He might ignore the error as
well as he might decide to empty the old data since the new one is more important, etc…
Here is a main program that uses the stack package and gets rid of some old data once the
stack is full:
with Stack; use Stack;
declare
Data: Integer;
begin
.
.
.
Push(Data);
.
.
exception
when Stack_Error =>
declare OldData : Integer;
begin
Pop(OldData);
Push(Data);
end;
end;
-74-
4-3 Parameter Passing Modes:
Ada’s parameter passing modes reflect the ways in which the programmer may intend to use
the parameter:
- Input (Read Only).
- Output (Write Only).
- Input-Output (Read-Write).
The in parameters (input) are like constants: never meant to be changed. Therefore, an
assignment to an in parameter generates a compiling error. By default, a parameter is passed
as in mode: procedure P(X: integer) is equivalent to procedure P(X: in
integer).
The out parameters are the opposite: they are meant to be read only. Early versions of Ada
(Ada 83) didn’t allow for reading an out parameter.
The in out parameters are meant to be read and modified inside a procedure.
The programmer specifies only one of these three modes to use. The implementation of
passing a parameter by reference, value or value-result is left to the compiler.
Copying
Compiler’s (Small
Decision parameters)
Reference
(Large
parameters)
In Out In-Out
Programmer’s Decision
-75-
4-4 Position-Independent Parameters
Ada introduced feature of randomly providing actual parameters. Suppose that we have a
procedure that needs 6 parameters:
Procedure Info(Name: array(1..30) of Character; Age: Integer;
Height: Integer; Weight: Integer; ShoeSize: Integer;
WilayaCode: Integer);
An actual call to that procedure might be like:
Info(‘Ali’, 26, 175, 75, 43, 1);
Ada make possible to deliver data arbitrarily:
Info(WilayaCode=> 1, Name=>‘Ali’, ShoeSize=>43, Height=>175,
Age=>26, Weight=>75);
Position-Independent parameters are another illustration of the Labeling Principle:
The Labeling Principle:
Avoid arbitrary sequences more than a few items long; do not require the programmer
to know the absolute position of an item in a list. Instead, associate a meaningful label (name)
with each item, and allow the items to occur in any order.
-76-
4-6 Concurrency: Non-Communicating Tasks
It is often more efficient and more convenient to do several things at the same time.
Let’s consider an example where a user is editing a file and printing another at once.
Depending on the used architecture, these two tasks can be run in parallel (multiple-
processors machine), or concurrently, i.e., in pseudo-parallel mode (single processor).
Ada provides tasking facilities to implement concurrency. Here is an example of a
procedure that runs two non-communicating tasks:
procedure WordProcessor is
task Edit; end Edit; -- Spec
task body Edit is -- Body
begin
. . . -- edit the selected file
end;
-77-
4-7 Concurrency: Communicating Tasks
Ada provides another tasking facility for communicating tasks, such as client/server
applications. The server provides some entry points to be called. This concept is defined as a
rendezvous between the server and the client.
task Server; -- Spec
entry SearchDatabase(X: Student);
entry ModifyDatabase(Y: out Student);
end Server;
The client is allowed to “call” the entry-point specified in the server’s specifications.
However, when the client calls a server’s service, it does not block or wit for the service to be
done; instead, it carries on its task. Therefore, an entry-point call is in fact a message-sending
not a regular procedure call:
task Client; end Client; -- Spec
task body Client is -- Body
begin
. . .
SearchDatabase(Ali); -- send the request to the server
. . . -- then continue the rest of its
. . . -- tasks
end;
-78-
In the body implementation of the server, each entry point is matched with an accept
clause that run the service of that entry-point. The server blocks at the first accept until its
entry point is been called by a user. Usually, accept clauses are controlled by a select
construct in order to choose whichever entry-point has been called:
task body server is -- Body
begin
loop
select
accept SearchDatabase(X: Student) do
. . .
end SearchDatabase;
accept ModifyDatabase(Y: out Student) do
. . .
end ModifyDatabase;
end select;
end loop;
end;
What happens when more than one client are calling an entry-point? A mailbox is
attached to each entry point and the server accepts them one by one in a FIFO order.
Client 1
Client 2
SearchDatabase SearchDatabase
Server
accept SearchDatabase
SearchDatabase
SearchDatabase
Client 3
Client 4
-79-
5- Ada’s Contributions:
- Strong, rich, secure typing system.
- Package facilities.
- Implementation of true ABSTRACT DATA TYPES.
- Modularity and separation between specification and implementation.
- Exception facilities.
- Tasking facilities.
- Secure and efficient parameter passing modes.
- Powerful: dynamic arrays, unconstrained arrays, array slice assignments.
- Genericity (static).
-80-
Fifth Generation Class of Languages :
1- Functional Paradigm: LISP
2- Structural Organization:
2-1 Primary Data Structure: The List.
LISP was designed to process lists of objects. These objects are either atoms
(elementary) or lists (composite):
(a b c d e f) a list of 6 atoms.
(a (b c) d (e f)) a list of 2 atoms and 2 lists.
(to be or not to be) a list of 6 atoms.
2-2 Program Representations:
In LISP, programs are also represented as lists. A function call can be easily presented
as a list:
(f par1 par2 par3…)
There are 2 famous expressions that represent function calls: The M-expression,
(Meta expression). In this mode, a function is preceded by a list of its parameters
(separated by commas). Also, an M-expression represents a regular arithmetic
operation in an infix notation (or order):
f(g(a, b) , h(c, d)) or (a – b) + (c * d)
LISP uses the S-expression (Symbolic expression) where a prefix order is applied for
both function calls and arithmetic operations:
(f (g a b) (h c d)) or (+ (- a b) (* c d))
In the S-expression, the first element of a program list is always a function;
which makes it easier for the interpreter to evaluate.
-81-
Here are some examples of commands interpretations in a LISP interpreter:
% (+ 2 5)
7
% (+ (- 4 1) 5)
8
% (- 4 7)
-3
% (set ‘x 5)
5
% x
5
% (= x 5)
t
% (> x 10)
nil
3- Data Structures:
2-1 The primitives: Atoms.
Atoms are the primitive type used in Lisp. An atom can be numeric (1, 2.0,
…) or a non-numeric name (a, to, …).
Common Lisp interpreter provides a predicate to test whether an object is an
atom or not:
% (atom 5)
t
% (atom ‘to)
t
% (atom ‘x) -- the character x
t
% (atom x) -- the variable x
Error : x not defined
% (set ‘x 5) -- defining x = 5
5
-82-
% (atom x)
t
% (set ‘x ‘(a b)) -- defining x = (a b)
(a b)
% (atom x)
nil
% (atom (+ 2 3)) -- is 5 an atom ?
t
% (atom ‘(+ 2 3))
nil
% (atom (a b c))
Error
% (atom ‘(a b c))
nil
When a list is quoted, the interpreter considers it as a list of items and never evaluates
it. Hence, Lisp distinguishes between a composite data structure and a program by the
quote: a quoted list is data; a non-quoted list is a program or a function call.
-83-
2-3 Data Representation.
Lisp used a very simple mechanism to represents lists: a linked-list where each item
has two pointers: The first pointer points on the first element of the list, whereas the
second points on the rest of the list:
(1 2 3)
1 2 3
((a b) c (d e) f)
c f
a b d e
()
Equivalent to
nil
The atom nil is used as a list terminator. For historical reasons, the empty list () is
considered equivalent to the atom nil:
% (eq ‘() nil)
t
% (null ‘())
t
% (null ‘(a b))
nil
-84-
% (atom nil)
t
% (listp ‘())
t
% (atom ‘())
t
3- List Manipulations:
3-1 Selector Operations:
Common Lisp provides two fundamental operations to access list items: car and cdr.
Car returns the first element of the list, no matter whether it is an atom or a list. Cdr
returns the list excluding the first item. Cdr returns always a list:
% (car ‘(a b c d))
a
% (car ‘((a b) c d))
(a b)
% (set ‘x ‘((a b) (c d)))
((a b) (c d))
% (car x)
(a b) -- the first item
% (cdr x)
((c d)) -- a list of the rest of the items
Car and cdr are pure functions: they don’t alter the accessed list. In addition, another
syntactic sugar was invented to simplify list manipulation:
(caaddadr x) is equivalent to
(car (car (cdr (cdr (car (cdr x))))))
Example :
% (set ‘AS ‘((Ali Said) 30 20000 (15 January 1970)))
((Ali Said) 30 20000 (15 January 1970))
% (cadr AS)
30 -- Age
-85-
% (caddadddr AS)
1970 -- Year of Birth
Explanation :
% (cdr ‘((Ali Said) 30 20000 (15 January 1970)))
(30 20000 (15 January 1970))
% (cdr ‘(30 20000 (15 January 1970)))
(20000 (15 January 1970))
% (cdr ‘(20000 (15 January 1970)))
((15 January 1970))
% (car ‘((15 January 1970)))
(15 January 1970)
% (cdr ‘(15 January 1970))
(January 1970)
% (cdr ‘(January 1970))
(1970)
% (car ‘(1970))
1970
-86-
B) append <list1> <list2> → <list>
It merges the items of two lists:
% (append ‘(a b) ‘(c d))
(a b c d)
% (append ‘((a b)) ‘(c d))
((a b) c d)
% (append ‘a ‘(b c d))
Error
% (append ‘(a b c) ‘d)
(a b c . d) -- weird notation: the last atom
-- is d instead of nil
-87-
B) rplacd <list> <list> → <list>
It appends the first item of a list with the second list:
% (rplacd ‘(a b c) ‘(x))
(a x)
Because of the list implementation using linked-lists, Lisp wasn’t quite ready to
remedy the consequences of the side effects of impure functions. Consider this
example:
% (set ‘L1 ‘(a b c d e f))
(a b c d e f)
L1
a b c d e f
L1 L2
a b c d e f
-88-
% (rplacd L2 ‘(x y))
(b x y)
L1 L2
a b c d e f
x y
% L1
(a b x y)
-89-
% (defun PGCD (x y)
(cond
((= x y) x)
((> x y) (PGCD (- x y) y))
(t (PGCD x (- y x)))
)
)
PGCD
% (PGCD 6 9)
3
As we can see, Lisp has altered the iteration with recursion since every iterative
program is, theoretically, transferable to some recursion function.
-90-
AA
% (getprop ‘age AA)
45
Setq is used to avoid quoting the first argument (the variable’s name)).
-91-
3-6 Association Lists:
Some properties do not require a value. For instances, flags (true or false) are
very common in use: retired, married, … In case of property lists, we have to provide
a value for these properties:
(name (Ali Ahmed) married t age 45 salary 30000 hiredate
(25 Jan 2008) retired nil)
This problem is solved by another data structure: the association list or a-list:
((a1 v1) (a2 v2)... (an vn)), where ai is an association property and vi is
its corresponding value. The function that extracts the associated value of a property
is called assoc:
% (setq AA ‘((name (Ali Ahmed)) (married) (age 45)
(salary 30000) (hiredate (25 Jan 2008))))
AA
% (assoc ‘age AA)
45
% (assoc ‘hiredate AA)
(25 Jan 2008)
% (assoc ‘married AA)
t
% (assoc ‘retired AA)
nil
-92-
% (defun pos (x) (>= x 0))
POS
% (mapcar ‘pos ‘(0 2 -5 6))
(t t nil t)
% (mapcar ‘listp ‘(a (b c) ((d e)) ))
(nil t t)
-93-
Exercices :
1- Write a function Length that counts the number of top-level elements in a list:
% (Length ‘(a b c d e f))
6
2- Write a function modprop that modifies a property with the provided value:
% (modprop ‘age 35 ‘(name (Ali Ahmed) age 45))
(name (Ali Ahmed) age 35)
4- Write a function sumup that adds up the elements of a given numeric list:
% (sumup ‘(1 2 3 4))
10
-94-
4- Lambda Expressions:
4-1 Anonymous Functions:
Let’s recall the example of mapcar that we saw previously:
% (defun inc (x) (+ x 1))
INC
% (mapcar ‘inc ‘(1 3 5))
(2 4 6)
% (defun pos (x) (>= x 0))
POS
% (mapcar ‘pos ‘(0 2 -5 6))
(t t nil t)
-95-
Based on the lambda calculus theory, Common Lisp provided a solution by
allowing nameless functions to be declared on the fly:
% (mapcar ‘(lambda(x) (+ x 1)) ‘(1 3 5))
(2 4 6)
% (mapcar ‘(lambda(x) (>= x 0)) ‘(0 2 -5 6))
(t t nil t)
% (setq x 0)
0
% (setq y 2)
2
% (mapcar ‘(lambda(x) (list x y)) ‘(1 3 5))
((1 2) (3 2) (5 2))
% (mapcar ‘(lambda(y) (list x y)) ‘(1 3 5))
((0 1) (0 3) (0 5))
-96-
% (defun listwith0 (y) ((bu ‘list 0) y))
LISTWITH0
% (LISTWITH0 1)
(0 1)
% (defun g(x y z) (+ x y z))
G
% (defun tu (f x y) (function (lambda(z) (f x y z))))
TU
% ((tu ‘g 2 3) 4)
9
Some dialects of Lisp, including Common Lisp, force the use of funcall in order to
call a formal parameter function, or a function that returns a function:
% (defun bu (f x) (function (lambda(y) (funcall f x y))))
BU
% (defun listwith0 (y) ((funcall (bu ‘list 0)) y))
LISTWITH0
% (LISTWITH0 1)
(0 1)
-97-
% (defun inc (x) (+ x 1))
INC
% (defun cube (x) (* (* x x) x))
INC
% (P INC CUBE)
15
% (P CUBE INC)
128
% (P CARREE DEC)
26
% (defun getprop (p L)
(cond
((null L) nil)
((eq p (car L)) (car (cdr L)))
(t (getprop p (cdr (cdr L))))
)
)
GETPROP
%(defun length (L)
(cond
((null L) 0)
(t (+ 1 (length (cdr L))))
)
)
LENGTH
%(Length ‘(a b c d e f))
6
-98-
% (defun modprop (prop newvalue L)
(cond
((null L) nil)
((eq (car L) prop) (cons prop (cons newvalue
(cddr L))))
(t (modprop prop newvalue (cddr L)))
)
)
GETPROP
-99-