Expressions and Assignment Statements
Chapter 7: Expressions and Assignment Statements
1. Introduction
Expressions are a fundamental means of specifying computations in a programming language.
To understand expression evaluation, it is essential to be familiar with the order of operator and operand evaluation.
The essence of imperative languages lies in the dominant role of assignment statements.
2. Arithmetic Expressions
Arithmetic evaluation was one of the motivations behind the development of the first programming languages.
Arithmetic expressions consist of:
Operators
Operands
Parentheses
Function calls
In most languages, binary operators are infix, except in languages like Scheme and LISP, where they are prefix. Perl also includes some prefix binary operators.
Most unary operators are prefix, but operators such as
++
and--
in C-based languages can be either prefix or postfix.
3. Design Issues Related to Arithmetic Expressions
Various design issues include:
Operator precedence rules?
Operator associativity rules?
Order of operand evaluation?
Operand evaluation side effects?
Operator overloading?
Type mixing in expressions?
4. Types of Operators
A unary operator has one operand.
A binary operator has two operands.
A ternary operator has three operands.
5. Operator Precedence Rules
Operator precedence rules define the order in which adjacent operators of different precedence levels are evaluated.
Typical precedence levels (from highest to lowest):
Parentheses
Unary operators
Exponentiation operator (** if supported by the language)
Multiplication (*) and Division (/)
Addition (+) and Subtraction (-)
6. Operator Associativity Rules
Operator associativity rules define the order in which adjacent operators with the same precedence level are evaluated.
Typical associativity rules include:
Left to right, except for exponentiation (**), which is right to left.
Some unary operators may associate right to left (e.g., in FORTRAN).
APL is unique in that all operators have equal precedence and associate right to left.
Precedence and associativity rules can be overridden with parentheses.
7. Expressions in Ruby and Scheme
Ruby: All arithmetic, relational, and assignment operators are implemented as methods.
This allows application programs to override these operators.
Scheme (and Common Lisp): All arithmetic and logic operations are conducted by explicitly called subprograms.
8. Conditional Expressions
Conditional expressions exist in C-based languages (like C, C++) and are structured as follows:
Example: average = (count == 0) ? 0 : sum / count
This evaluates as:
if (count == 0)
average = 0
else average = sum / count
9. Operand Evaluation Order
Operand evaluation order is as follows:
Variables fetch the value from memory.
Constants may fetch from memory or be embedded in machine language instructions.
Parenthesized expressions first evaluate all operands and operators.
Function calls may introduce complexities in evaluation order.
10. Potential for Side Effects
Functional side effects occur when a function changes a two-way parameter or a non-local variable.
Issue: If a function changes another operand, it can lead to confusion.
Example:
a = 10; b = a + fun(&a);
In this instance, assuming
fun
alters its parameter creates complications.
11. Solutions to Functional Side Effects
Two possible solutions include:
Disallowing functional side effects:
Prevent two-way parameters in functions.
No non-local references in functions.
Advantages: Effective control.
Disadvantages: Inflexibility due to one-way parameters.
Enforcing a fixed operand evaluation order:
Disadvantage: Limits compiler optimizations.
Java mandates that operands appear to be evaluated in a specific order.
12. Referential Transparency
A program maintains referential transparency if any two expressions with the same value can replace each other without affecting the program's outcome.
Example:
result1 = (fun(a) + b) / (fun(a) - c); temp = fun(a); result2 = (temp + b) / (temp - c);
If
fun
has no side effects,result1
will equalresult2
.
Advantages of Referential Transparency:
Easier program semantics understanding.
Pure functional languages are referentially transparent as they lack state stored in local variables.
Functions can only use constants.
13. Overloaded Operators
Operator overloading refers to the use of an operator for multiple purposes.
Some overloads are common (e.g.,
+
forint
andfloat
).Others can cause trouble (e.g.,
*
in C and C++, may lead to loss of error detection).
Languages like C++, C#, and F# allow user-defined overloaded operators, which can enhance readability when used sensibly.
Potential issues:
Users can define nonsensical operations.
Readability may suffer even with sensible operations.
14. Type Conversions
A narrowing conversion converts an object to a type that cannot represent all original values (e.g., converting
float
toint
).A widening conversion converts to a type that can represent approximations of all original values (e.g., converting
int
tofloat
).
15. Mixed Mode Expressions
A mixed-mode expression contains operands of different types.
A coercion is an implicit type conversion.
Disadvantages: Decreased error detection ability in compilers.
Typically, most languages coerce numeric types using widening conversions.
In ML and F#, coercions are generally not allowed in expressions.
16. Explicit Type Conversions
Explicit type conversions are often referred to as casting in C-based languages.
Examples:
C:
(int)angle
F#:
float(sum)
Note: F# has a syntax similar to function calls for casting.
17. Errors in Expressions
Common causes of errors include:
Inherent limitations of arithmetic (e.g., division by zero).
Limitations of computer arithmetic (e.g., overflow).
Often, these errors are overlooked by the run-time system.
18. Relational and Boolean Expressions
Relational Expressions:
Utilize relational operators with various types of operands.
Evaluate to a Boolean representation.
Operator symbols may differ among languages (e.g.,
!=
,/=
,~=
).JavaScript and PHP feature additional operators:
===
and!==
.These operators do not coerce operands.
19. Boolean Expressions
Boolean expressions:
Operands are Boolean; the result is also Boolean.
Example operators include those from C89, which does not have a Boolean type but utilizes the
int
type with 0 for false and nonzero for true.An example of C's expressions:
Evaluating
a < b < c
is legal but does not yield expected results; the left operator is evaluated first.
20. Short-Circuit Evaluation
Short-circuit evaluation allows an expression's result to be determined without fully evaluating all operands and/or operators.
Example:
if (a == 0) return;
, wherea
is checked first, and if true, the whole expression short-circuits.
Problem with non-short-circuit evaluation:
Example:
index = 0; while (index <= length && LIST[index] != value) index++;
If
index
equalslength
, it will lead to an out-of-bounds error.
21. Short-Circuit Evaluation in Languages
Languages such as C, C++, and Java utilize short-circuit evaluation for standard Boolean operators (
&&
and||
) but also provide bitwise Boolean operators that do not short-circuit (&
and|
).In Ruby, Perl, ML, F#, and Python, all logical operators are short-circuit evaluated.
Short-circuit evaluation raises potential side effect issues in expressions (e.g., evaluating
(a > b) || (b++ / 3)
).
22. Assignment Statements
The general syntax for assignment statements is:
The assignment operator
=
varies in use across languages. For instance:Fortran, BASIC:
=
C-based languages:
:=
The
=
operator can be problematic when overloaded for equality (hence C-based languages use==
for relational equality).
23. Conditional Targets in Assignment Statements
Conditional targets exist in languages like Perl:
Example:
($flag ? $total : $subtotal) = 0;
Equivalent to:
if ($flag) { $total = 0 } else { $subtotal = 0 }
24. Compound Assignment Operators
Compound assignment operators provide shorthand for common assignment forms.
Introduced in ALGOL; widely adopted by C and similar languages.
Example:
a = a + b
can be simplified to:a += b;
25. Unary Assignment Operators
Unary assignment operators (in C-based languages) combine increment and decrement operations with assignment.
Examples:
sum = ++count;
(count incremented, then assigned)sum = count++;
(count assigned, then incremented)count++;
(post-increment)-count++;
(count incremented then negated)
26. Assignment as an Expression
In languages like C-based languages, Perl, and JavaScript, assignment can produce a result and be used as an operand.
Example:
while ((ch = getchar()) != EOF) { ... }
Here,
ch = getchar()
executes and the result will be used for the conditional check.
Disadvantage: This creates a different form of side effect in expressions.
27. Multiple Assignments
Languages like Perl and Ruby support multiple-target, multiple-source assignments as follows:
Example:
($first, $second, $third) = (20, 30, 40);
Additionally, an assignment can interchange values:
($first, $second) = ($second, $first);
28. Assignment in Functional Languages
In functional languages, identifiers are only names of values.
ML:
Names are bound to values using
val
.val fruit = apples + oranges;
If another
val
forfruit
follows, it creates a new and different name.
F#:
let
functions similarly toval
, but creates a new scope.
29. Mixed-Mode Assignment
Assignment statements can involve mixed-mode assignments.
In languages such as Fortran, C, Perl, and C++, any numeric type value can be assigned to any numeric type variable.
In Java and C#, only widening assignment coercions are permitted.
Ada does not allow assignment coercion.
30. Summary
This chapter covered the following topics:
Expressions
Operator precedence and associativity
Operator overloading
Mixed-type expressions
Various forms of assignment