1 operand | 2 operands | 3 operands | . . . | |
adicity | monadic | diadic | triadic | . . . |
arity | unary | binary | ternary | . . . |
For example, both mathematics and many languages use -
both for
subtraction and for negation. Some languages make an effort to distinguish
these, e.g. using ~
for negation. Even so, almost all languages have
some overloading; e.g. (
and )
are used both for bracketed
expressions
and for lists (e.g. parameters) or tuples (or even array accesses).
These different uses can normally only be distinguished by the context in
the representation - e.g. whether the -
is used as a monadic or
diadic operator, or whether the (
is preceded by an identifier or by
another operator. Other kinds of overloading can happen because, as in
mathematics, the same symbol is used for e.g. addition of integers, reals
and matrices - again, these different uses can only be distinguished by
context, but this time in the meaning e.g. the types of identifiers.
The greatest variation is found in the precedence of logical operations such
as and
and or
. Some languages give them the same precedence,
whereas most give and
higher precedence than or
. Even then,
some languages give multiplication and and
, and addition and
or
the same precedence, whereas most give arithmetic operations
higher precedence than logical operations.
Overloading also can cause precedence problems. Some languages give monadic
-
(negation) higher precedence than diadic -
(subtraction)
whereas other languages give them the same precedence.
An example of an exception to the general rule that infix operators are left-associative is exponentiation (raising to a power, not e), which is found in some languages.
Prefix operators are usually right-associative, and postfix operators left-associative.
Some languages also have non-associative operators i.e. a series of these operators is not permitted unless they are explicitly bracketed. The commonest example is with relational operators.
The concept of associativity is not relevant in languages where operators and their operands are already bracketed together, thus making the sequence explicit.
There are even languages where all operators are non-associative, so complex expressions must be explicitly bracketed - this simplifies translation, but is initially irritating to the user.
Precedence and associativity can actually be expressed by the rules describing representations, but this is not generally the case when we are defining meaning.
There are only two circumstances where this matters:
the first occurs with side-effects - if an item in an expression
can change the value of another item in the expression, either directly
or via a function that can modify global variables or its parameters.
The second occurs in an expression of the form:
if the access is illegal or the accessed value is the one wanted ...
Some languages specify the order in which sub-expressions are evaluated
(usually left-to-right),
some minimise the problem by not allowing side-effects, and
some just expect the user to write code that will not be affected by the
order of evaluation.
Languages that do not specify evaluation order are usually defined this way
so that compilers can rewrite expressions to optimise the code they generate.
Such languages often deal with any resulting problems by defining special
operators which use lazy evaluation.
To be useful, lazy operators must have a specific order of evaluation of their sub-expressions (usually left-to-right).
Not all languages have conditional expressions, but most of those that do use
lazy evaluation: a ? b : c
will first evaluate a
, and then
either evaluate b
or c
but not both. Very few languages use
eager evaluation, in which both b
and c
are evaluated and then
one value or the other discarded.
A language with lazy conditional expressions does not actually need lazy
boolean (e.g. and
or
) operators as well, as their effect can be
simulated, but it is convenient to have them also available.
A system based on lazy evaluation is MAKE, which, given one or more targets, evaluates only those intermediates that are necessary to produce the targets, and even then only those that depend on sources that have changed, regardless of how many different intermediates are described within its MAKEfile.
Something similar goes on inside spread-sheets; when the value of any cell is changed, a good spread-sheet will only recalculate the values of those cells that are directly or indirectly affected.
( ) [( ) = bracketed expression] { ( ) [ ] -> . [( ) = function call] { ++ - - [monadic, postfix, L-assoc] ! ~ + - * & (type) sizeof ++ - - [monadic, prefix, R-assoc] * / % + - << >> < <= > >= == != & ^ | && [lazy L->R] || [lazy L->R] ?: [triadic, lazy L->R, R-assoc] = += -= *= /= %= &= ^= |= <<= >>= [R-assoc] , [evaluate L->R]
! % & $ # + - / : < = > ? @ \ ~ ' ^ | *
Because of this, it is hard to be as specific about SML as about C, but here is my attempt. Again, operators seem to be mainly diadic, infix, left associative, with the order of evaluation of operands undefined - exceptions noted below:
( ) / div mod * + - ^ :: @ [:: is R-assoc] = <> < > <= >= := o [:= is R-assoc] andalso [lazy L->R] orelse [lazy L->R] if-then-else [triadic, lazy L->R, R-assoc]
This implies that:
In the next few sections you will learn how to use them via expression processing examples. We can then comment on the interaction of expression design and implementation, although there is one point worth making now:
+=
and ++
are for succinctness and
clarity, not implementation efficiency