Essential component of ``high-level'' languages.
Most familiar for arithmetic operators.
Abstract away from precise order of evaluation, naming of intermediate results.
Issue: Precedence rules (handled in parsing).
Issue: Mixed-mode expressions and implicit coercions.
Many languages extend ``high-level'' expression facility to non-arithmetic values, such as booleans.
Operands: true, false, boolean-valued variables.
Operators: and, or, not.
Contexts: wherever a boolean value makes sense (ifs, wheres, etc.)
Remember that booleans are typically a separate type (C/C++ is an exception).
Issue: Does language use short-circuit evaluation for boolean expressions?
a AND b : evaluate b only if a evaluates to true.
a OR b : evaluate b only if a evaluates to false.
Common misuse of booleans:
Richer expression domains
Some languages support expressions over larger values, e.g., vector, strings, etc.
More generally, can view operators as just special way of denoting functions. So, to define expressions over an arbitrary type, just define appropriate operator functions.
Operator syntax, precedence, etc. may be fixed for language or programmer-definable.
Issues like sharing, storage management are tricky.
Not all operators act like functions.
Statement-level Control Structures
Primary mechanisms developed in FORTRAN and ALGOL60; mostly minor changes since then (30+ years).
Talk of control ``structures'' as opposed to ``structureless'' code using goto's and indirect jumps (``spaghetti code'').
Concurrent computation may be more ``natural'' (for brains and hardware) but appears hard to reason about accurately!
Machine-level Control Flow
Sequencing; unless otherwise directed, do the next instruction.
Labels, i.e., addresses in target code.
Arithmetic and logical IF ? THEN GOTO constructs.
These more than suffice to compute anything that can be computed (as best we know).
(e.g., Edsger Dijkstra, ``Go to statement considered harmful,'' CACM, 11(3), March 1968, 147-148.)
Branches (conditional and unconditional) suffice to program anything; they are what machines use.
BUT problems are best solved in terms of higher-level constructs, such as loops and conditional blocks.
Program text should make programmer's intent explicit.
Static structure of program text should resemble dynamic structure of program execution.
Undisciplined use of GOTO's makes these goals hard to achieve.
(Not just ``GOTOs are bad.'') Structured Programming--Basic Elements
Can also put test at end. Sometimes want it in the middle...
Using exit violates single-exit goal. If loops are nested, want ability to exit any number of levels.
When are bounds calculated? Are they recalculated?
Can <statements> change value of i
Does i have a defined value after the end loop?
Can one jump into or out of loop?
What if upper-bound is less than lower-bound to start with?
can be optimized better than
Iteration is Recursion
We can give recursive definitions to the meaning of iterative statements.
is equivalent to
Any iteration can be converted to a recursion.
The converse is not true in general. But any tail-recursion (such as the one above) can be converted into an iteration. Any decent compiler should take advantage of this (though many don't). Conditionals and Cases
(Various parts can be missing.)
Permits more efficient code (a jump table) if values are ``dense.''
That's All, Folks!
This small set of statements suffices for nearly all programs.
Completely unrestricted jumps are seldom allowed.
It makes little sense to allow jumps into the middle of a block, since none of the block-local storage will have been properly initialized.
Many languages permit jumps out to enclosing blocks; in a stack allocation scheme, such jumps require quietly popping one or more frames.
Most languages provide special forms of escapes from structured program components, such as loop exit.
These discourage uses of goto, but some good uses remain. Uses for goto
Problem: Given a key value k, search an array a for a matching entry and increment the corresponding element of an array b. If not found, add the new key to the end of a.
A solution with goto:
A solution with booleans:
This is clumsier and slower.
A solution with one-level exit.
This is better, but still requires testing found below the loop.
A solution with multi-level exit.
Pretend we can exit from any named enclosing block.
This does the trick. But is it any better than the original goto version? The COME FROM statement
(R. Lawrence Clark, ``A linguistic contribution to GOTO-less programming,'' Datamation, 19(12), 1973, 62-63.)
But is this really a joke?
Even with a GO TO, we must examine both the branch and the target label to understand the programmer's intent. Exceptions
Programs often need to handle exceptional conditions, i.e., deviations from ``normal'' control flow.
Exceptions may arise from
failure of built-in or library operations (e.g., division by zero, end of file)
user-defined events (e.g., key not found in dictionary)
Awkward or impossible to deal with these conditions explicitly without distorting normal code.
Most recent languages (Ada, C++, Java, etc.) provide a means to define, raise, and handle exceptions.
What to do in an exceptional case?
In most languages, uncaught exceptions propagate to next dynamically
enclosing handler. E.g, caller can handle uncaught exceptions raised in callee.
A few languages support resumption of the program at the point where the exeption was raised.
Java provides a try...finally construct:
Fun with C
Problem: Sending characters to an output device as quickly as possible.
(Avoids compare with n each time.) Faster to unroll loop, say 4 times:
Or (the Duff Loop):
``This is the most amazing piece of C I've ever seen.'' - Ken Thompson