Concepts of Programming Languages: Expressions and Assignment Statements

Foundations of Expressions and Evaluation

  • Expressions are the fundamental means by which computations are specified within a programming language.

  • Understanding expression evaluation requires familiarity with the specific orders of operator and operand evaluation.

  • In imperative programming languages, the assignment statement plays a dominant and essential role in the language's essence.

Arithmetic Expressions: Structure and Design Issues

  • Arithmetic evaluation was a primary motivator for the development of the very first programming languages.

  • Composition: Arithmetic expressions consist of operators, operands, parentheses, and function calls.

  • Operator Placement and Arity:     - A unary operator has one operand.     - A binary operator has two operands.     - A ternary operator has three operands.     - In most languages, binary operators use infix notation.     - In Scheme and LISP, binary operators use prefix notation.     - Perl also includes some prefix binary operators.     - Unary operators are typically prefix, but the ++ and -- operators in C-based languages can be either prefix or postfix.

  • Design Issues for Arithmetic Expressions:     - Determining operator precedence rules.     - Determining operator associativity rules.     - Defining the order of operand evaluation.     - Managing operand evaluation side effects.     - Handling operator overloading.     - Addressing type mixing within expressions.

Operator Precedence and Associativity

  • Operator Precedence Rules: These rules define the order in which "adjacent" operators of different precedence levels are evaluated.

  • Typical Precedence Levels (Highest to Lowest):     1. Parentheses     2. Unary operators     3. ** (if the language supports exponentiation)     4. *, / (Multiplication and Division)     5. +, - (Addition and Subtraction)

  • Operator Associativity Rules: These rules define the evaluation order for adjacent operators that share the same level of precedence.     - Typical Rules: Left to right evaluation, except for **, which usually associates right to left.     - Language Variations: Unary operators sometimes associate right to left (e.g., in FORTRAN).     - APL Exception: In APL, all operators have equal precedence and all associate right to left.

  • Precedence and associativity rules can be explicitly overridden through the use of parentheses.

Language-Specific Expression Models

  • Ruby:     - All arithmetic, relational, and assignment operators are implemented as methods.     - Array indexing, shifts, and bit-wise logic operators are also methods.     - A consequence of this design is that application programs can override all of these operators.

  • Scheme and Common Lisp:     - All arithmetic and logic operations are performed via subprograms that are explicitly called.

  • Conditional Expressions (C-Based Languages):     - Examples include C and C++.     - Example syntax: average=(count==0)?0:sum/countaverage = (count == 0)? 0 : sum / count.     - This evaluates as follows: if(count==0)extaverage=0extelseextaverage=racsumcountif (count == 0) ext{ average } = 0 ext{ else } ext{ average } = rac{sum}{count}.

Operand Evaluation and Functional Side Effects

  • Rules for Operand Evaluation:     - Variables: The value is fetched from memory.     - Constants: The value is fetched from memory or is embedded within the machine language instruction.     - Parenthesized Expressions: All internal operands and operators are evaluated first.     - The most complex case occurs when an operand is a function call.

  • Functional Side Effects: These occur when a function modifies a two-way parameter or a non-local variable.     - Problem: A functional side effect occurs when a function referenced in an expression alters another operand within that same expression.     - Example for a parameter change:         - a=10a = 10         - /* ext{Assume } fun ext{ changes its parameter } */         - b=a+fun(&a)b = a + fun(\&a)

  • Potential Solutions:     1. Disallow functional side effects in the language definition by banning two-way parameters and non-local references in functions.         - Advantage: This solution is effective.         - Disadvantage: It creates inflexibility due to the lack of non-local references and the limitations of one-way parameters.     2. Demand that the operand evaluation order be fixed in the language definition.         - Disadvantage: This limits various compiler optimizations.         - Note: Java requires that operands appear to be evaluated in a fixed order.

Referential Transparency

  • Definition: A program has the property of referential transparency if any two expressions in the program with the same value can be substituted for one another anywhere in the program without affecting the program's action.

  • Comparison Example:     - result1=racfun(a)+bfun(a)cresult1 = rac{fun(a) + b}{fun(a) - c}     - temp=fun(a)temp = fun(a)     - result2=ractemp+btempcresult2 = rac{temp + b}{temp - c}     - If funfun possesses no side effects, then result1=result2result1 = result2. If side effects exist, referential transparency is violated.

  • Advantages: Program semantics are much easier to understand when referential transparency is maintained.

  • Functional Languages: Programs in pure functional languages are referentially transparent because they do not have variables. Functions cannot have state (stored in local variables), and values used from outside must be constants.

Operator Overloading and Type Conversions

  • Operator Overloading: The use of an operator for more than one purpose.     - Common examples: Use of + for both intint and floatfloat types.     - Troubling examples: The * operator in C and C++ can lead to a loss of readability and compiler error detection (e.g., omitting an operand should be a detectable error).

  • User-Defined Overloading: C++, C#, and F# allow for user-defined overloaded operators.     - Advantage: Can aid readability and make expressions appear natural by avoiding explicit method calls.     - Disadvantage: Users can define nonsense operations, and readability may suffer even if the operator usage makes sense.

  • Narrowing Conversion: Converts an object to a type that cannot include all values of the original type (e.g., floatfloat to intint).

  • Widening Conversion: Converts an object to a type that can include at least approximations of all values of the original type (e.g., intint to floatfloat).

  • Mixed-Mode Expressions: An expression containing operands of different types.

  • Coercion: An implicit type conversion.     - Disadvantage: Coercions decrease the compiler's ability to detect type errors.     - In most languages, numeric types are coerced in expressions using widening conversions.     - Exceptions: In ML and F#, there are no coercions in expressions.

  • Explicit Type Conversions (Casting): Called "casting" in C-based languages.     - Example (C): (int)angle(int)angle.     - Example (F#): float(sum)float(sum) (Note that F# syntax is similar to function call syntax).

Relational and Boolean Expressions

  • Relational Expressions: Use relational operators and operands of various types to evaluate to a Boolean representation.     - Variable Symbols: !=, /=, ~=, .NE., <>, #.     - JavaScript and PHP: Feature identity operators === and !==. These are similar to == and != but do not perform coercion on their operands.

  • Boolean Expressions: Operands and results are both Boolean.     - C89: Lacks a Boolean type; it utilizes the intint type, where 00 represents false and any nonzero value represents true.     - Odd Characteristic of C: The expression a < b < c is legal. It evaluates the left operator first (producing 00 or 11), and the result is then compared with cc.

  • Errors in Expressions: Often caused by inherent arithmetic limitations (e.g., division by zero) or computer arithmetic limitations (e.g., overflow). These are frequently ignored by the run-time system.

Short-Circuit Evaluation

  • Definition: An expression in which the result is determined without evaluating all of its operands and/or operators.

  • Example: (13imesa)imes(racb131)(13 imes a) imes ( rac{b}{13} - 1). If aa is zero, evaluating the second half (racb131)( rac{b}{13} - 1) is unnecessary.

  • Potential Problems with Non-Short-Circuit Evaluation:     - index=0index = 0     - while (index eq length) ext{ } && ext{ } (LIST[index] eq value) ext{ } index++     - If index=lengthindex = length, evaluating LIST[index]LIST[index] will cause an indexing problem (assuming the list is length1length - 1 long).

  • Language Support:     - C, C++, and Java: Use short-circuit evaluation for Boolean operators (&& and ||) but provide bitwise operators (& and |) that are not short-circuit.     - Ruby, Perl, ML, F#, and Python: All logic operators are short-circuit evaluated.

  • Risk: Short-circuit evaluation can expose problems related to side effects, e.g., (a > b) ext{ } || ext{ } (b++ / 3).

Assignment Statements and Syntax

  • General Syntax: <target_var> <assign_operator> <expression>.

  • Assignment Operators:     - =: Used in Fortran, BASIC, and C-based languages.     - :=: Used in Ada.

  • Conflict: The use of = can be problematic when it is overloaded for the relational equality operator; thus, C-based languages use == for relational equality.

  • Conditional Targets (Perl):     - Example: (ext$flag?ext$total:ext$subtotal)=0( ext{\$flag} ? ext{\$total} : ext{\$subtotal}) = 0.     - Equivalent logic: if(ext$flag)extext$total=0extelseextext$subtotal=0if ( ext{\$flag}) ext{ } ext{\$total} = 0 ext{ } else ext{ } ext{\$subtotal} = 0.

  • Compound Assignment Operators: A shorthand for specifying common assignments, introduced in ALGOL and adopted by C-based languages (e.g., a=a+ba = a + b can be a+=ba += b).

  • Unary Assignment Operators (C-based): Combine increment/decrement with assignment.     - sum=++countsum = ++count (count incremented, then assigned to sum).     - sum=count++sum = count++ (count assigned to sum, then incremented).     - count++count++ (count incremented).     - count++-count++ (count incremented then negated).

  • Assignment as an Expression: In C-based languages, Perl, and JavaScript, the assignment statement produces a result.     - Example: while((ch=getchar())eqEOF)ext...while ((ch = getchar()) eq EOF) ext{ } .... The result of ch=getchar()ch = getchar() is used as a conditional value.     - Disadvantage: Creates another form of expression side effect.

  • Multiple Assignments (Perl and Ruby): Allows multiple-target, multiple-source assignments.     - Example: (ext$first,$second,$third)=(20,30,40)( ext{\$first, \$second, \$third}) = (20, 30, 40).     - Interchange: (ext$first,$second)=(ext$second,$first)( ext{\$first, \$second}) = ( ext{\$second, \$first}) is legal and performs a swap.

Assignment in Functional Languages

  • Identifiers in functional languages are names of values only.

  • ML:     - Names are bound to values using val (e.g., valextfruit=apples+orangesval ext{ fruit} = apples + oranges).     - If a subsequent val for fruit appears, it is considered a new and different name.

  • F#:     - Uses let, which is similar to val in ML, except that let also creates a new scope.

Mixed-Mode Assignment Coercion Rules

  • Assignment statements can be mixed-mode.

  • Fortran, C, Perl, and C++: Any numeric type value can be assigned to any numeric type variable.

  • Java and C#: Only widening assignment coercions are performed.

  • Ada: No assignment coercion is permitted.