'''''
This botch is the biggest single problem with Pascal. I believe that if it
could be fixed, the language would be an order of magnitude more usable. The
proposed ISO standard for Pascal 13
provides such a fix (``conformant array schemas''''''''), but the acceptance of this
part of the standard is apparently still in doubt.
2.2. There are no static variables and no initialization
A static variable (often called an own variable in Algol-speaking countries)
is one that is pri vate to some routine and retains its value from one call of
the routine to the next. De facto, For tran variables are internal static,
except for COMMON;# in C there is a static declaration that can be applied to
local variables. __________________
# Strictly speaking, in Fortran 77 one must use SAVE to force the static
attribute.
Pascal has no such storage class. This means that if a Pascal function or
procedure intends to remember a value from one call to another, the variable
used must be external to the function or procedure. Thus it must be visible
to other procedures, and its name must be unique in the larger scope. A
simple example of the problem is a random number generator: the value used to
compute the current output must be saved to compute the next one, so it must
be stored in a vari able whose lifetime includes all calls of the random
number generator. In practice, this is typi cally the outermost block of the
program. Thus the declaration of such a variable is far removed from the
place where it is actually used.
One example comes from the text formatter described in Chapter 7 of Tools. The
variable dir controls the direction from which excess blanks are inserted
during line justification, to obtain left and right alternately. In Pascal,
the code looks like this:
program formatter (...);
var dir : 0..1; { direction to add extra spaces } . . . procedure justify
(...); begin dir := 1 - dir; { opposite direction from last time } ... end;
...
begin { main routine of formatter } dir := 0; ... end;
The declaration, initialization and use of the variable dir are scattered all
over the program, liter ally hundreds of lines apart. In C or Fortran, dir
can be made private to the only routine that needs to know about it:
... main() { ... }
...
justify() { static int dir = 0;
dir = 1 - dir; ... }
There are of course many other examples of the same problem on a larger scale;
functions for buffered I/O, storage management, and symbol tables all spring
to mind.
There are at least two related problems. Pascal provides no way to initialize
variables stati cally (i.e., at compile time); there is nothing analogous to
Fortran''''s DATA statement or initializers like
int dir = 0;
in C. This means that a Pascal program must contain explicit assignment
statements to initialize variables (like the
dir := 0;
above). This code makes the program source text bigger, and the program
itself bigger at run time.
Furthermore, the lack of initializers exacerbates the problem of too-large
scope caused by the lack of a static storage class. The time to initialize
things is at the beginning, so either the main routine itself begins with a
lot of initialization code, or it calls one or more routines to do the
initializations. In either case, variables to be initialized must be visible,
which means in effect at the highest level of the hierarchy. The result is
that any variable that is to be initialized has glo bal scope.
The third difficulty is that there is no way for two routines to share a
variable unless it is declared at or above their least common ancestor.
Fortran COMMON and C''''s external static stor age class both provide a way for
two routines to cooperate privately, without sharing informa tion with their
ancestors.
The new standard does not offer static variables, initialization or
non-hierarchical commu nication.
2.3. Related program components must be kept separate
Since the original Pascal was implemented with a one-pass compiler, the
language believes strongly in declaration before use. In particular,
procedures and functions must be declared (body and all) before they are used.
The result is that a typical Pascal program reads from the bottom up -- all
the procedures and functions are displayed before any of the code that calls
them, at all levels. This is essentially opposite to the order in which the
functions are designed and used.
To some extent this can be mitigated by a mechanism like the #include facility
of C and Ratfor: source files can be included where needed without cluttering
up the program. #include is not part of standard Pascal, although the UCB, VU
and Whitesmiths compilers all provide it.
There is also a forward declaration in Pascal that permits separating the
declaration of the function or procedure header from the body; it is intended
for defining mutually recursive proce dures. When the body is declared later
on, the header on that declaration may contain only the function name, and
must not repeat the information from the first instance.
A related problem is that Pascal has a strict order in which it is willing to
accept declara tions. Each procedure or function consists of
label label declarations, if any const constant declarations, if any type type
declarations, if any var variable declarations, if any procedure and function
declarations, if any begin body of function or procedure end
This means that all declarations of one kind (types, for instance) must be
grouped together for the convenience of the compiler, even when the programmer
would like to keep together things that are logically related so as to
understand the program better. Since a program has to be presented to the
compiler all at once, it is rarely possible to keep the declaration,
initialization and use of types and variables close together. Even some of
the most dedicated Pascal supporters agree: 14
``The inability to make such groupings in structuring large programs is one of
Pascal''''s most frustrating limitations.''''''''
A file inclusion facility helps only a little here.
The new standard does not relax the requirements on the order of declarations.
2.4. There is no separate compilation
The ``official'''''''' Pascal language does not provide separate compilation, and so
each imple mentation decides on its own what to do. Some (the Berkeley
interpreter, for instance) disallow it entirely; this is closest to the spirit
of the language and matches the letter exactly. Many others provide a
declaration that specifies that the body of a function is externally defined.
In any case, all such mechanisms are non-standard, and thus done differently
by different systems.
Theoretically, there is no need for separate compilation -- if one''''s compiler
is very fast (and if the source for all routines is always available and if
one''''s compiler has a file inclusion facility so that multiple copies of source
are not needed), recompiling everything is equivalent. In practice, of
course, compilers are never fast enough and source is often hidden and file
inclusion is not part of the language, so changes are time-consuming.
Some systems permit separate compilation but do not validate consistency of
types across the boundary. This creates a giant hole in the strong typing.
(Most other languages do no cross compilation checking either, so Pascal is
not inferior in this respect.) I have seen at least one paper (mercifully
unpublished) that on page n castigates C for failing to check types across
sepa rate compilation boundaries while suggesting on page n+1 that the way to
cope with Pascal is to compile procedures separately to avoid type checking.
The new standard does not offer separate compilation.
2.5. Some miscellaneous problems of type and scope
Most of the following points are minor irritations, but I have to stick them
in somewhere.
It is not legal to name a non-basic type as the literal formal parameter of a
procedure; the following is not allowed:
procedure add10 (var a : array [1..10] of integer);
Rather, one must invent a type name, make a type declaration, and declare the
formal parameter to be an instance of that type:
type a10 = array [1..10] of integer; ... procedure add10 (var a : a10);
Naturally the type declaration is physically separated from the procedure that
uses it. The disci pline of inventing type names is helpful for types that
are used often, but it is a distraction for things used only once.
It is nice to have the declaration var for formal parameters of functions and
procedures; the procedure clearly states that it intends to modify the
argument. But the calling program has no way to declare that a variable is to
be modified -- the information is only in one place, while two places would be
better. (Half a loaf is better than none, though -- Fortran tells the user
nothing about who will do what to variables.)
It is also a minor bother that arrays are passed by value by default -- the
net effect is that every array parameter is declared var by the programmer
more or less without thinking. If the var declaration is inadvertently
omitted, the resulting bug is subtle.
Pascal''''s set construct seems like a good idea, providing notational
convenience and some free type checking. For example, a set of tests like
if (c = blank) or (c = tab) or (c = newline) then ...
can be written rather more clearly and perhaps more efficiently as
if c in [blank, tab, newline] then ...
But in practice, set types are not useful for much more than this, because the
size of a set is strongly implementation dependent (probably because it was so
in the original CDC implementa tion: 59 bits). For example, it is natural to
attempt to write the function isalphanum(c) (``is c alphanumeric?'''''''') as
{ isalphanum(c) -- true if c is letter or digit } function isalphanum (c :
char) : boolean; begin isalphanum := c in [''''a''''..''''z'''', ''''A''''..''''Z'''', ''''0''''..''''9''