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):

    1. Parentheses

    2. Unary operators

    3. Exponentiation operator (** if supported by the language)

    4. Multiplication (*) and Division (/)

    5. 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 equal result2.

  • 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., + for int and float).

    • 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 to int).

  • A widening conversion converts to a type that can represent approximations of all original values (e.g., converting int to float).

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;, where a 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 equals length, 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 for fruit follows, it creates a new and different name.

  • F#:

    • let functions similarly to val, 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