12.2 Assignment Statement

To support an assignment statement in Camille, we add the following rules to the grammar and corresponding pattern-action rules to the PLY parser generator:

A grammar rule and the corresponding pattern-action rule.
Description
A set of three code lines in Camille.
Description

It is helpful to draw a distinction between binding and variable assignment. A binding associates a name with a value. A variable assignment, in contrast, is a mutation of the expressed value stored in a memory cell. For instance, an identifier x can be associated with a reference, where a reference is an expressed value containing or referring to another expressed value 1. Mutating the value that the reference contains or to which the reference refers from 1 to 2 does not alter the binding of x to the reference (i.e., x is still bound to the same reference). A reference is called an L-value and an expressed value is known as an R-value—based on the side of the assignment statement in which each appears.

Variable assignment is helpful for a variety of purposes. For instance, two or more functions can communicate with each other through a shared “global” variable rather than by passing the variable back and forth to each other. This use of variables can reduce the number of parameters that need to be passed in a program. Of course, the use of variable assignment involves side effect, so there is a trade-off between data protection and the overhead of parameter-passing. However, we can use closures to protect that shared state from any unintended outside interference:

A set of 21 code lines in Camille with closures to protect shared state from any unintended outside interference.
Description

Here, the variable i is a private variable representing a counter. The identifier counter is bound to a Camille closure. In consequence, it remembers values in its lexical parent—here, i—even though the lifetime of that parent has expired (i.e., been popped off the stack).

12.2.1 Use of Nested lets to Simulate Sequential Evaluation

Since we do not yet have support for sequential evaluation or statement blocks in Camille (we add it in Section 12.7), we use nested lets to simulate sequential evaluation as demonstrated in the following example. The hypothetical Camille expression

A set of seven code lines in Camille with the function let.
Description

can be rewritten as an actual Camille expression:

A set of 12 code lines which is an actual Camille expression with the function let.
Description

The identifier ignored receives the return value of the two assignment statements. The return value of the assignment statement in C and C++ is the value of the expression on the right-hand side of the assignment operator.

12.2.2 Illustration of Pass-by-Value in Camille

We will modify the Camille interpreter so that parameters to functions are represented as references in the environment. We start by creating a new reference for each parameter in each function call—a parameter-passing mechanism called pass-by-value. As a result of the use of this new reference, modifications to the parameter within the body of the function will have no effect on the value of the parameter in the environment in which the parameter was passed as an argument; in other words, assignments will only have “local” effect. For instance, consider the following Camille program:

A set of 11 code lines in Camille with the function increment incrementing the copy of n.
Description

Here, a copy of n is passed to and incremented by the function increment, so the value of the n in the outermost let expression is not modified. Similarly, consider a swap function in Camille:

A set of 17 code lines in Camille with the function swap.
Description

Here, the values of a and b are not swapped because both are passed to the swap function by value.

12.2.3 Reference Data Type

To support an assignment statement in Camille, we must add a Reference data type, with interface dereference and assignreference to the interpreter. We use the familiar list-of-values (used in the list-of-lists, ribcage and abstract-syntax representations of an environment) for each rib (Friedman, Wand, and Haynes 2001). References are elements of lists, which are assignable using the assignment operator in Python. Again, note that lists in Python are used and accessed as if they were vectors rather than lists in Scheme, ML, or Haskell. In particular, unlike lists used in functional programming, the individual elements of lists in Python can be directly accessed through an integer index in constant time. Figure 12.1 depicts an instance of this Reference data type in relation to the underlying Python list used in its implementation. The following is an abstract-syntax implementation of a reference data type:

An illustration of a reference and a Python list.

Figure 12.1 A primitive reference to an element in a Python list.

Data from Friedman, Daniel P., Mitchell Wand, and Christopher T. Haynes. 2001. Essentials of Programming Languages. 2nd ed. Cambridge,MA: MIT Press.

Description
A set of 18 code lines in Python with the reference data type.
Description

The function dereference here is the analog of the * (dereferencing) operator in C/C++ (e.g., *x) when preceding a variable reference. However, unlike in C/C++, dereferencing is implicit in Camille, akin to referencing Scheme or Java objects. Thus, the function dereference is called within the Camille interpreter, but not directly by Camille programmers. In Scheme:

Two expressions in Scheme. Expressed value equals any possible Scheme value. Denoted value equals reference to any possible Scheme value.

so that

An expression. Denoted value is not equal to expressed value.

Scheme exclusively uses references as denoted values in the sense that all denoted values are references in Scheme.

In Java:

Two expressions in Java. Expressed value equals reference to object union primitive value. Denoted value equals reference to object union primitive value.

so that

An expression. Denoted value equals expressed value.

Java is slightly less consistent than Scheme in the use of references: all denoted values in Java, save for primitive values, are references. While all denoted values in Scheme are references, it appears to the Scheme programmer as if all denoted values are the same as expressed values because Scheme uses automatic or implicit dereferencing. Similarly, while all denoted values, save for primitives, are references in Java, it appears to the Java programmer as if all denoted values are the same as expressed values because Java also uses implicit referencing.

The functions dereference and assignreference are defined through primitive_dereference and primitive_assignreference because later we will reuse the latter two functions in implementations of references.

12.2.4 Environment Revisited

Now that we have a Reference data type, we must modify the environment implementation so that it can make use of references. We assume that denoted values in an environment are of the form Ref(x) for some x. We realize this environment structure by adding the function apply_environment_reference to the environment interface. This function is similar to apply_environment, except that when it finds the matching identifier, it returns the “reference to its value” instead of its value (Friedman, Wand, and Haynes 2001). Therefore, as in Scheme, all denoted values in Camille are references:

An expression. Denoted value is not equal to expressed value, equals integer union closure.

Thus,

An expression. Denoted value is not equal to expressed value, equals integer union closure.

The function apply_environment then can be defined through the apply_environment_reference and dereference (Friedman, Wand, and Haynes 2001) functions:

A set of 20 code lines with the function apply underscore environment defined through apply underscore environment underscore reference and dereference.
Description

Notice that we are using an abstract-syntax representation (ASR) of a named environment here. To complete the implementation of variable assignment, we add the following case to the evaluate_expr function:

A set of five code lines with the function evaluate underscore e x p r.
Description

Notice that a value is returned. Here, we explicitly return the integer 1 (as seen in the last line of code) because the return value of the function assignreference is unspecified and we must always return an expressed value. When using assignment statements in a variety of programming languages, the return value can be ignored (e.g., x--; in C). In Camille, the return value of an assignment statement is ignored, especially when a series of assignment statements are used within a series of let expressions to simulate sequential execution, as illustrated in this section.

12.2.5 Stack Object Revisited

Consider the following enhancement, using references, of a simple stack object in Camille as presented in Section 11.2.4:

A set of 43 code lines in Camille for the enhancement of a simple stack object.
Description
Continuation of the code in Camille for the enhancement of a simple stack object, consisting of 54 lines.
Description

In this version of the stack object, the stack is a true object because its methods are encapsulated within it. Notice that the let expression on lines 41–61 builds and returns a closure that simulates an array (of stack functions): It accepts an index i as an argument and returns the stack function located at that index.

Table 12.1 summarizes the properties of the new versions of the Camille interpreter developed in the programming exercises in this section.

Table 12.1 New Versions of Camille, and Their Essential Properties, Created in the Programming Exercises of This Section (Key: ASR = abstract-syntax representation; CLS = closure.)

A table of the descriptions and representations of Camille in different programming exercises.
Description

Conceptual and Programming Exercises for Section 12.2

Exercise 12.2.1 In the version of Camille developed in this section, we stated that denoted values are references to expressed values. Does this mean that references to expressed values are stored in the environment of the Camille interpreter developed in this section? Explain.

Exercise 12.2.2 Write a Camille program that defines the mutually recursive functions iseven? and isodd? (i.e., each function invokes the other). Neither of these functions accepts any arguments. Instead, they communicate with each other by changing the state of a shared “global” variable n that represents the number being checked. The functions should each decrement the variable n throughout the lifetime of the program until it reaches 0—the base case. Thus, the functions iseven? and isodd? communicate by side effect rather than by returning values.

Exercise 12.2.3 (Friedman, Wand, and Haynes 2001, Exercise 3.41, p. 103) In Scheme and Java, everything is a reference (except for primitives in Java), although both languages use implicit (pointer) dereferencing. Thus, it may appear as if no denoted value represents a reference in these languages. In contrast, C has reference (e.g., int* intptr;) and non-reference (e.g., int x;) types and uses explicit (pointer) dereferencing (e.g., *x). Thus, an alternative scheme for variable assignment in Camille is to have references be expressed values, and have allocation, dereferencing, and assignment operators be explicitly used by the programmer (as in C):

Two expressions in C. Expressed value equals integer union closure union reference to an expressed value. Denoted value equals expressed value.

Modify the Camille interpreter of this section to implement this alternative design, with the following new primitives:

  • cell: creates a reference

  • contents: dereferences a reference

  • assigncell: assigns a reference

In this version of Camille, the counter program at the beginning of Section 12.2 is rendered as follows:

A set of 11 code lines in Camille for the counter program at the beginning.
Description

Exercise 12.2.4 (Friedman, Wand, and Haynes 2001, Exercise 3.42, p. 105) Add support for arrays to Camille. Modify the Camille interpreter presented in this section to implement arrays. Use the following interface for arrays:

  • array: creates an array

  • arrayreference: dereferences an array

  • arrayassign: updates an array

Thus,

Three expressions in Camille. Array equals a list of zero or more references to expressed values. Expressed value equals integer union closure union array. Denoted value equals reference to an expressed value.

Note that the first occurrence of “reference” (on the right-hand side of the equal sign in the first equality expression) can be a different implementation of references than that described in this section. For example, a Python list is already a sequence of references.

What is the result of the following Camille program?

A set of nine code lines in a Camille program.
Description
Continuation of the code in a Camille program consisting of nine lines.
Description

Exercise 12.2.5 Rewrite the Camille stack object program in Section 12.2.5 so that it uses arrays. Specifically, eliminate the closure that simulates an array (of stack functions) built and returned through the let expression on lines 41–60 and use an array instead to store the collection of stack functions. Use the array-creation and -manipulation interface presented in Programming Exercise 12.2.4.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.26.138