6.9 Mixing Lexically and Dynamically Scoped Variables

Dynamic scoping was used in McCarthy’s original version of Lisp (more on this in Section 6.10) as well as in APL and SNOBOL4. Scheme, a dialect of Lisp, adopted static scoping.2 Some languages, including Perl and Common Lisp, leave the scoping method up to the programmer. However, Perl gives the programmer a finer level of control over scope. Control of scope is done on the variable level, rather than the program level. Instead of setting the method of scoping for the entire program, Perl enables the programmer to fine-tune which variables are statically scoped and which are dynamically scoped. The provisions for scope in Common Lisp are similar. Qualifiers, including private, public, and friend, and operators, including the scope resolution operator ::, give the programmer a finer control over the scope of a declaration in C++.

We use the Perl program in Listing 6.2 to demonstrate this mixture. The main program (i.e., the program code before the definitions of procedures proc1 and proc2) prints the values of two variables (l and d) to standard output, calls proc1, increments l and d, and again prints the values of two variables (l and d) to standard output. In the definition of proc1, the my qualifier on the declaration of the variable l specifies that l is a lexically scoped variable. This means that any reference to l in proc1 or any blocks nested therein follow the lexical scoping rule given previously, unless there is an intervening declaration of l. The local qualifier on the declaration of the variable d specifies that d is a dynamically scoped variable. This means that any reference to d in proc1 or any procedure called from proc1 or called from that procedure, and so on, is bound to this declaration of d unless there is an intervening declaration of d. Thus, the first two lines of program output are

A set of two lines of a program output.
Description

We see a reference to l and d on line 37 in the definition of proc2, which does not provide declarations for l and d. To resolve those references so that we can evaluate the print statement, we must determine to which declarations the references to l and d are bound.

Examining this program, we see that the only possible run-time call sequence of procedures is main → proc1proc2. Figure 6.4 depicts the run-time stack at the time of the print statement on line 37. While static scoping involves a search of the program text, dynamic scoping involves a search of the run-time stack. Specifically, while determining the declaration to which a reference is bound in a lexically scoped language involves an outward search of the nested blocks enclosing the block where the reference is made, doing the same in a dynamically scoped language involves a downward search from the top of the stack to the bottom. Using the approach outlined in Section 6.4.1 for determining the declaration associated with a reference to a lexically scoped variable, we discover that the reference to l on line 37 is bound to the declaration of l on line 1. Since d is a dynamically scoped variable and d is not declared in the definition of proc2, we must search back through the call chain. Examining the definition of the procedure that called proc2 (i.e., proc1), we find a declaration for d. Thus, our search is complete and we use the denotation of d in proc1 at the time proc2 is called: 20. Therefore, proc2 prints

A table of activation records and variables for different procedure names.

Figure 6.4 Depiction of run-time stack at call to print on line 37 of Listing 6.2.

Description
The output printed by p r o c 2 is as follows. Inside the call to p r o c 2 dash dash dash 1 colon 10, d colon 20.

The output of the entire program is

Five lines of output of a program.
Description

Dynamic scoping means that the declaration to which a reference is bound cannot be determined until run-time. However, in Listing 6.2, we need not run the program to determine to which declaration the reference to d on line 37 is bound. Even though the variable d is dynamically scoped, we can determine the call chain of the procedures, before run-time, by examining the text of the program. However, in most programs we cannot determine the call chain of procedure before run-time—primarily due to the presence of run-time input.

Listing 6.3 A Perl program, whose run-time call chain depends on its input, demonstrating dynamic scoping.

Consider the Perl program in Listing 6.3, which is similar to Listing 6.2 except that the call chain depends on program input. If the input is 5, then the call chain is

A call chain is as follows: main, right arrow, p r o c 1, right arrow, p r o c 2.

and the output is the same as the output for Listing 6.2. Otherwise, the call chain is

A call chain is as follows: main, right arrow, p r o c 2.

and the output is

Three lines of output in a Perl program.
Description

As we can see, just because we can determine the declaration to which a reference is bound before run-time in a particular program, that does not mean that the language in which the program is written uses static scoping.

Listings 6.2 and 6.3 contain both shadowed lexical and dynamic declarations and, therefore, lexical and dynamic scope holes, respectively. For instance, the declaration of l on line 20 in Listing 6.2 shadows the declaration of l on line 1. Furthermore, the declaration of d on line 24 in Listing 6.2 shadows the declaration of d on line 2, creating a scope hole in the definition of proc1 as well as any of the functions it or its descendants (on the stack) call. In other words, the shadow is cast into proc2. In contrast, the declaration of l on line 20 in Listing 6.2 does not create scope holes in any descendant procedures.

Conceptual and Programming Exercises for Section 6.9

Exercise 6.9.1 Identify all of the scope holes on lines 4, 11, 29, 33, and 37 of Listing 6.2. For each of those lines, state which declarations create shadows and indicate the declarations they obscure.

Exercise 6.9.2 Identify all of the scope holes on lines 7, 18, 36, 40, 44 of Listing 6.3. For each of those lines, state which declarations create shadows and indicate the declarations they obscure.

Exercise 6.9.3 Sketch a graph depicting the lexical structure of the procedures (i.e., a lexical graph), including the body of the main program, in Listing 6.2.

Exercise 6.9.4 Sketch the static call graph for Listing 6.2. Is the static call graph for Listing 6.3 the same? If not, give the static call graph for Listing 6.3 as well.

Exercise 6.9.5 Consider the following Scheme, C, and Perl programs, which are analogs of each other:

A set of four code lines in Scheme, C, and Perl program.
Description
A set of 10 code lines in Scheme, C, and Perl program.
Description
Continuation of the code in Scheme, C, and Perl program, consisting of three code lines.
Description
A set of 14 code lines in Scheme, C, and Perl program.
Description

The following graph depicts the lexical structure of these three programs (i.e., a lexical graph):

An illustration of a call graph. Arrows from lambda or main lead to p r o c 1 and p r o c 2.

The rules in Scheme, C, and Perl that specify which procedures have access to call other procedures are different. Therefore, while each program has the same lexical structure, they may not have the same static call graph.

Sketch the static call graph for each of these programs.

Exercise 6.9.6 Consider the following Scheme, C, and Perl programs, which are analogs of each other:

A set of five code lines in Scheme, C, and Perl program.
Description
A set of 14 code lines in Scheme, C, and Perl program.
Description
A set of 12 code lines.
Description

The following graph depicts the lexical structure of these three programs (i.e., a lexical graph):

An illustration of a call graph. Arrows lead to lambda or main, p r o c 1, and p r o c 2.

The rules in Scheme, C, and Perl that specify which procedures have access to call other procedures are different. Therefore, while each program has the same lexical structure, they may not have the same static call graph.

Sketch the static call graph for each of these programs.

Exercise 6.9.7 Does line 4 [i.e., print p r o c 2, left parenthesis, p r o c 1, left parenthesis, dollar sign x, right parenthesis, asterisk, dollar sign y, right parenthesis, semicolon.] of the Perl program in Exercise 6.9.6 demonstrate that Perl supports first-class functions? Explain why or why not.

Exercise 6.9.8 Common Lisp, like Perl, allows the programmer to declare statically or dynamically scoped variables. Figure out how to set the scoping method of a variable in a Common Lisp program and write a Common Lisp program that illustrates the difference between static and dynamic scoping, similarly to the Perl programs in this section. (Do not replicate that program in Common Lisp.) Use GNU CLISP implementation of Common Lisp, available at https://clisp.sourceforge.io/. Writing a program that only demonstrates how to set the scoping method in Common Lisp is insufficient.

2. This is an example of mutation in the evolution of programming languages.

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

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