Author : Ian Joyner
Page : << Previous 15 Next >>
recognise the
word to represent a computer language. The case of the letters does not
change the semantics. Letter case is only a typographic device.
Typographic conventions make program text more readable, but should not
affect the semantics of a program. Case distinction is based on the low
level paradigm of character codes such as ASCII used internally in the
computer. This weakens the purpose of using names to replace addresses,
as names are reduced to a string of character codes.
Case distinction also contributes to errors. It introduces
ambiguity, and as has already been mentioned, ambiguity weakens the
purpose of names, as identity is lost. As every programmer will have
experienced, one character errors are more difficult to find than one
would think. For example if an identifier is declared Fred, another one
can be declared fred. Such names are easily mistyped and confused. We
are in general poor proof-readers. The psychological reason for this is
that the the brain tends to straighten out errors for our perception
automatically. The human brain is an excellent instrument for working
out what was intended, even in the presence of radical error. (This
makes us good at difficult tasks like speech recognition.) In order to
overcome this programmers must use their powers of concentration to
override this natural tendency of the brain. Distinguishing upper from
lower case in names only adds another level of difficulty. Good
language design takes into account such psychological considerations in
these small but important details, being designed towards the way humans
work, not computers. Such considerations of cognitive science make a
big difference to the effectiveness of people, but do not have any
impact at all on the efficiency of code generated for the computer.
What is more important, people or computers?
Case distinction provides another form of name overloading. Name
overloading is a double-edged sword. It leads to ambiguity, confusion
and error. Name overloading as has been suggested in the section on
name overloading should only be provided in controlled and expected
ways, where overloading provides a useful function such as module
independence or polymorphism. Where a name is overloaded in the same
scope the compiler should report an error.
As another example, a commonly used technique is -
class obj
{
int Entry;
void set_entry (int entry)
{
entry = Entry;
}
}
If you have not spotted the error in the above example, what was it
supposed to mean?
6.12. Assignment Operator
Using the mathematical equality symbol for the assignment operator is
a poor choice of symbols. Programming assignment is not equal to
mathematical equality (:= != =). Language designers of ALGOL style
languages realised they were semantically quite different, so took the
care to distinguish, only using '=' to mean equality in the sense of
mathematical assertion. In C the lack of distinction leads to error.
It is easy to use = (assignment) where == (equality) is intended, which
looks reasonable, but results in errors that are difficult to detect.
This leads to a more general criticism of C, in that it has a pseudo
mathematical appearance. Few people are proficient at interpreting
mathematical theorems, most passing over such sections in text, making
the assumption that the mathematics proves the surrounding text. The
pseudo-mathematical nature of C has this bad attribute of mathematical
notation. It is difficult to read, while lacking the semantic
consistency and precision of mathematical notation. One of the keys of
reusability is readability.
6.13. Type Casting
Type casting is just a mechanism to map values of one type onto
values of another type. This means type casting is no more than a
specific form of mathematical function. Type casting has been useful in
computer systems. Often it is required to map one type onto another,
where the bit representation of the value remains the same. Type
casting is therefore a trick to optimise certain operations. Type
casting provides no useful concept that general functions cannot
implement. Furthermore, type casting undermines the purpose of strongly
typed systems. In many languages, the type system has not been
consistently defined, so programmers often feel that type casting is
necessary.
Mathematically, all functions perform type casting. An example often
used in programming is to cast between characters and integers. Type
casts between integers and characters are easily expressed as functions
using abstract data types (ADTs).
TYPE
CHARACTER
FUNCTIONS
ord: CHARACTER -> INTEGER // convert input character to integer
char: INTEGER /-> CHARACTER // convert input integer to character
PRECONDITION
// check i is in range
pre char (i: INTEGER) = 0 <= i and i <= ord (last character)
The notation '->' means every character will map to an integer. The
partial function notation '/->' means that not every integer will map to
a character, and a precondition, given in the 'pre char' statement,
specifies the subset of integers that maps to characters.
Object-oriented syntax provides this consistently with member functions
on a class:
i : INTEGER
ch : CHARACTER
i := ch.ord // i becomes the integer value of the character.
ch := i.char // ch becomes the character corresponding to
// the value i.
but a routine char would probably not be defined on the integer type
so this would more likely be:
ch.char (i); // set ch to the character corresponding to the
// value i.
The hardware of many machines cater for such basic data types as
character and integer, and it is entirely possible that a compiler will
generate code that is optimal for any target hardware architecture. So
many languages have character and integer as built in types. The
object-oriented paradigm, however, can treat such basic data types
consistently and elegantly, by the implicit definition of their own
classes.
Another example of type conversion is from real to integer. Here
though, the programmer might wish to specify the use of two type
conversion functions to truncate or round.
TYPE
REAL
FUNCTIONS
truncate: REAL -> INTEGER
round: REAL -> INTEGER
r: REAL
i: INTEGER
i := r.truncate // i becomes the closest integer <= r
i := r.round // i becomes the closest integer to r
Again many hardware platforms provide specific instructions to
achieve this, and an efficient object-oriented language compiler will
generate code best suited to the target machine. Such inbuilt class
definitions might be a part of the standard language definition.
6.14. Semicolons
I am not concerned whether the semicolon is defined as a terminator
or separator. Arguments that languages that define the semicolon as
terminator are superior to those that define it as separator are,
however, baseless. The semicolon as separator is really quite logical.
It is based on viewing the semicolon as a statement sequencing or
concatenation operator. It is therefore a binary operator, requiring
both a left and a right hand side. Some people claim to find this
concept difficult to understand, but if we consider it in the context of
a mathematical expression, it would be silly to expect that an addition
be written as:
a + b +
Another way to look at a separator is to consider the structure of a
program. A program is a list of elements. The executable part of a
program is a list of sequentially executed instructions. Elements in a
list must be separated, and the semicolon is one syntax to separate
elements in a list. The semicolon is therefore part of the syntax of
the list, not part of the syntax of the individual instructions.
Languages such as FORTRAN separated instructions by requiring that they
be placed on different lines or cards. If an instruction overflowed a
line, a continuation character was required, like the backslash in C.
Well defined languages do not require continuation characters, as line
breaks are unimportant, and have no effect on semantics. Languages
should have very regular grammars, so that the semicolon could be an
entirely optional typographic separator.
In natural language both the comma and semicolon are separators, only
the full stop is a terminator. If the comma was a terminator, function
invocations would look like:
fn (a, b+c, d, e,);
It is often argued that the semicolon as separator leads to
irregularities. C's handling of the grammar of semicolons, however,
leads to an irregularity in if/else's:
if (condition)
statement1; /* Semicolon required */
else
statement2;
if (condition)
{
statement1;
} /* Semicolon must be omitted */
else
statement2;
This is an irregularity, as a parser will reduce both of the above to
the grammatical form:
if (condition) statement else statement
(In fact why do conditions
Page : << Previous 15 Next >>