0% found this document useful (0 votes)
13 views45 pages

Unit 3

This document discusses subprograms, their characteristics, and parameter passing methods in programming languages. It outlines the definitions, types (procedures and functions), and design issues related to subprograms, as well as various parameter passing techniques like pass-by-value and pass-by-reference. Additionally, it highlights the implications of local referencing environments and the implementation of parameter-passing methods across different programming languages.

Uploaded by

rsanthi457
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views45 pages

Unit 3

This document discusses subprograms, their characteristics, and parameter passing methods in programming languages. It outlines the definitions, types (procedures and functions), and design issues related to subprograms, as well as various parameter passing techniques like pass-by-value and pass-by-reference. Additionally, it highlights the implications of local referencing environments and the implementation of parameter-passing methods across different programming languages.

Uploaded by

rsanthi457
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 45

UNIT-III SUBPROGRAMS AND IMPLEMENTATIONS

SUBPROGRAMS:
Introduction
Subprograms are the fundamental building blocks of programs and are therefore
among the most import concepts in programming language design.
The reuse results in several different kinds of savings, including memory space and
coding time.
Fundamentals of Subprograms
General Subprogram Characteristics
a. A subprogram has a single entry point.
b. The caller is suspended during execution of the called subprogram, which implies
that there is only one subprogram in execution at any given time.
c. Control always returns to the caller when the called subprogram’s execution
terminates
Basic Definitions
A subprogram definition is a description of the actions of the subprogram
abstraction.
A subprogram call is an explicit request that the called subprogram be executed.
A subprogram is said to be active if, after having been called, it has begun
execution but has not yet completed that execution.
The two fundamental types of the subprograms are:
o Procedures
o Functions
A subprogram header is the first line of the definition, serves several definitions:
o It specifies that the following syntactic unit is a subprogram definition of some
particular kind.
o The header provides a name for the subprogram.
o May optionally specify a list of parameters.
Consider the following header examples:
Fortran
Subroutine Adder(parameters)
Ada
procedure Adder(parameters)
C
void Adder(parameters)
No special word appears in the header of a C subprogram to specify its kind.
The parameter profile (sometimes called the signature) of a subprogram is the
number, order, and types of its formal parameters.
The protocol of a subprogram is its parameter profile plus, if it is a function, its return
type.
A subprogram declaration provides the protocol, but not the body, of the
subprogram.
A formal parameter is a dummy variable listed in the subprogram header and used
in the subprogram.
An actual parameter represents a value or address used in the subprogram call
statement.
Function declarations are common in C and C++ programs, where they are called
prototypes.
Java and C# do not need declarations of their methods, because there is no
requirement that methods be defined before they are called in those languages.
Parameters
Subprograms typically describe computations. There are two ways that a non-local
method program can gain access to the data that it is to process:
1. Through direct access to non-local variables.
The only way the computation can proceed on different data is to
assign new values to those non-local variables between calls to the
subprograms.
Extensive access to non-locals can reduce reliability.
2. Through parameter passing “more flexible”.
Data passed through parameters are accessed through names that are
local to the subprogram.
A subprogram with parameter access to the data it is to process is a
parameterized computation.
It can perform its computation on whatever data it receives through its
parameters.
A formal parameter is a dummy variable listed in the subprogram header and
used in the subprogram.
Subprograms call statements must include the name of the subprogram and a list of
parameters to be bound to the formal parameters of the subprogram.
An actual parameter represents a value or address used in the subprogram call
statement.
Actual/Formal Parameter Correspondence:
1. Positional: The first actual parameter is bound to the first formal parameter
and so forth. “Practical if list is short.”
2. Keyword: the name of the formal parameter is to be bound with the actual
parameter. “Can appear in any order in the actual parameter list.”
SORT(LIST => A, LENGTH => N);
Advantage: order is irrelevant
Disadvantage: user must know the formal parameter’s names.
Default Values:
procedure SORT(LIST : LIST_TYPE;
LENGTH : INTEGER := 100);
...
SORT(LIST => A);
In C++, which has no keyword parameters, the rules for default parameters are
necessarily different.
The default parameters must appear last, for parameters are positionally associated.
Once a default parameter is omitted in a call, all remaining formal parameters must
have default values.
float compute_pay(float income, float tax_rate,
int exemptions = 1)
An example call to the C++ compute_pay function is:
pay = compute_pay(20000.0, 0.15);
Procedures and Functions
There are two distinct categories of subprograms, procedures and functions.
Procedures: provide user-defined parameterized computation statements.
The computations are enacted by single call statements.
Procedures can produce results in the calling program unit by two methods:
o If there are variables that are not formal parameters but are still visible in both
the procedure and the calling program unit, the procedure can change them.
o If the subprogram has formal parameters that allow the transfer of data to the
caller, those parameters can be changed.
Functions: provide user-defined operators which are semantically modeled on
mathematical functions.
o If a function is a faithful model, it produces no side effects.
o It modifies neither its parameters nor any variables defined outside the
function.
The methods of Java, C++, and C# are syntactically similar to the functions of C.
Design Issues for Subprograms:
1. What parameter passing methods are provided?
2. Are parameter types checked?
3. Are local variables static or dynamic?
4. Can subprogram definitions appear in other subprogram definitions?
5. What is the referencing environment of a passed subprogram?
6. Can subprograms be overloaded?
7. Are subprograms allowed to be generic?

Local Referencing Environments:


Vars that are defined inside subprograms are called local vars.
Local vars can be either static or stack dynamic “bound to storage when the program
begins execution and are unbound when execution terminates.”
Advantages of using stack dynamic:
a. Support for recursion.
b. Storage for locals is shared among some subprograms.
Disadvantages:
a. Allocation/deallocation time.
b. Indirect addressing “only determined during execution.”
c. Subprograms cannot be history sensitive “can’t retain data values of local vars
between calls.”
Advantages of using static vars:
a. Static local vars can be accessed faster because there is no indirection.
b. No run-time overhead for allocation and deallocation.
c. Allow subprograms to be history sensitive.
Disadvantages:
a. Inability to support recursion.
b. Their storage can’t be shared with the local vars of other inactive
subprograms.
In C functions, locals are stack-dynamic unless specifically declared to be static.
Ex:
int adder(int list[ ], int listlen) {
static int sum = 0; //sum is static variable
int count; //count is stack-dynamic
for (count = 0; count < listlen; count++)
sum += list[count];
return sum;
}
Ada subprograms and the methods of C++, Java, and C# have only stackdynamic local
variables.

Parameter Passing Methods:


Semantic Models of Parameter Passing
Formal parameters are characterized by one of three distinct semantic models:
o in mode: They can receive data from corresponding actual parameters.
o out mode: They can transmit data to the actual parameter.
o inout mode: They can do both.
There are two conceptual models of how data transfers take places in parameter
transmission:
o Either an actual value is copied (to the caller, to the callee, or both ways), or
o An access path is transmitted.
Most commonly, the access path is a simple pointer or reference.
Figure below illustrates the three semantics of parameter passing when values are
copied.
Implementation Models of Parameter Passing:
1. Pass-by-Value
When a parameter is passed by value, the value of the actual parameter is used to
initialize the corresponding formal parameter, which then acts as a local var in the
subprogram, thus implementing in-mode semantics.
Disadvantages:
Additional storage is required for the formal parameter, either in the called
subprogram or in some area outside both the caller and the called subprogram.
The actual parameter must be copied to the storage area for the corresponding
formal parameter. “If the parameter is large such as an array, it would be costly.
2. Pass-by-Result
Pass-by-Result is an implementation model for out-mode parameters.
When a parameter is passed by result, no value is transmitted to the subprogram.
The corresponding formal parameter acts as a local var, but just before control is
transferred back to the caller, its value is transmitted back to the caller’s actual
parameter, which must be a var.
One problem with the pass-by-result model is that there can be an actual parameter
collision, such as the one created with the call.
sub(p1, p1)
In sub, assuming that the two formal parameters have different names, the two can
obviously be assigned different values.
Then whichever of the two is copied to their corresponding actual parameter last
becomes the value of p1.
3. Pass-by-Value-Result
It is an implementation model for inout-mode parameters in which actual values are
copied.
It is a combination of pass-by-value and pass-by-result.
The value of the actual parameter is used to initialize the corresponding formal
parameter, which then acts as a local var.
At subprogram termination, the value of the formal parameter is transmitted back to
the actual parameter.
It is sometimes called pass-by-copy because the actual parameter is copied to the
formal parameter at subprogram entry and then copied back at subprogram
termination.
4. Pass-by-reference
Pass-by-reference is a second implementation model for inout-mode parameters.
Rather than copying data values back and forth. This method transmits an access
path, sometimes just an address, to the called subprogram. This provides the
access path to the cell storing the actual parameter.
The actual parameter is shared with the called subprogram.
Advantages:
The passing process is efficient in terms of time and space. Duplicate space is
not required, nor is any copying.
Disadvantages:
Access to the formal parameters will be slower than pass-by-value, because of
additional level of indirect addressing that is required.
Inadvertent and erroneous changes may be made to the actual parameter.
Aliases can be created as in C++.
void fun(int &first, int &second)
If the call to fun happens to pass the same var twice, as in
fun(total, total)
Then first and second in fun will be aliases.
5. Pass-by-Name
The method is an inout-mode parameter transmission that doesn’t correspond to a
single implementation model.
When parameters are passed by name, the actual parameter is, in effect, textually
substituted for the corresponding formal parameter in all its occurrences in the
subprogram.
A formal parameter is bound to an access method at the time of the subprogram
call, but the actual binding to a value or an address is delayed until the formal
parameter is assigned or referenced.
Because pass-by-name is not used in any widely used language, it is not discussed
further here

Parameter-Passing Methods of Major Languages:


Fortran
o Always used the inout semantics model
o Before Fortran 77: pass-by-reference
o Fortran 77 and later: scalar variables are often passed by value-result
C
o Pass-by-value
o Pass-by-reference is achieved by using pointers as parameters
C++
o A special pointer type called reference type for pass-by-reference
void fun(const int &p1, int p2, int &p3) { … }
Java
o All parameters are passed are passed by value
o Object parameters are passed by reference
Although an object reference passed as a parameter cannot itself be
changed in the called subprogram, the referenced object can be
changed if a method is available to cause the change.
Ada
o Three semantics modes of parameter transmission: in, out, in out; in is the
default mode
o Formal parameters declared out can be assigned but not referenced; those
declared in can be referenced but not assigned; in out parameters can be
referenced and assigned
C#
o Default method: pass-by-value
o Pass-by-reference is specified by preceding both a formal parameter and its
actual parameter with ref
PHP: very similar to C#
Perl: all actual parameters are implicitly placed in a predefined array named @_

Implementing Parameter-Passing Methods:


In most contemporary languages, parameter communication takes place through the
run-time stack.
The run-time stack is initialized and maintained by the run-time system, which is a
system program that manages the execution of programs.
The run-time stack is used extensively for subprogram control linkage and
parameter passing.
Pass-by-value parameters have their values copied into stack locations.
The stack location then serves as storage for the corresponding formal parameters.
Pass-by-result parameters are implemented as the opposite of pass-by-value.
The values assigned to the pass-by-result actual parameters are placed in the stack,
where they can be retrieved by the calling program unit upon termination of the
called subprogram.
Pass-by-value-result parameters can be implemented directly from their semantics
as a combination pf pass-by-value and pass-by-result.
The stack location for the parameters is initialized by the call and it then used like a
local var in the called subprogram.
Pass-by-reference parameters are the simplest to implement.
Only its address must be placed in the stack.
Access to the formal parameters in the called subprogram is by indirect addressing
from the stack location of the address.
Figure below illustrates the previous parameters’ passing methods.
The subprogram sub is called from main with the call sub(w, x ,y, z), where w is
passed by value, x is passed by result, y is passed by value-result, and z is
passed by reference.
A subtle but fatal error can occur with pass-by-reference and pass-by-value-result
parameters if care is not take in their implementation.
Suppose a program contains two references to the constant 10, the first as an actual
parameter in a call to a subprogram.
Further suppose that the subprogram mistakenly changes the formal parameter that
corresponds to the 10 to the value 5.
The compiler may have built a single location for the value 10 during compilation, as
compilers often do, and use that location for all references to the constant 10 in the
program.
But after the return from the subprogram, all subsequent occurrences of 10 will
actually be references to the value 5.
If this is allowed to happen, it creates a programming problem that is very difficult to
diagnose.
This happened in many implementations of Fortran IV.
Parameters that are Subprogram Names
In languages that allow nested subprograms, such as JavaScript, there is another
issue related to subprogram names that are passed as parameters.
The question is what referencing environment for executing the passed subprogram
should be used.
The three choices are:
1. It is the environment of the call statement that enacts the passed subprogram
“Shallow binding.”
2. It is the environment of the definition of the passed subprogram “Deep binding.”
3. It is the environment of the call statement that passed the subprogram as an
actual parameter ”Ad hoc binding; has never been used”
Ex: “written in the syntax of Java”
function sub1( ) {
var x;
function sub2( ) {
alert(x); // Creates a dialog box with the value of x
};
function sub3( ) {
var x;
x = 3;
sub4(sub2);
};
function sub4(subx ) {
var x;
x = 4;
subx( );
};
x = 1;
sub3( );
};
Consider the execution of sub2 when it is called in sub4.
Shallow Binding: the referencing environment of that execution is that of sub4, so
the reference to x in sub2 is bound to the local x in sub4, and the output of the
program is 4.
Deep Binding: the referencing environment of sub2’s execution is that of sub1, so
the reference so the reference to x in sub2 is bound to the local x in sub1 and the
output is 1.
Ad hoc: the binding is to the local x in sub3, and the output is 3.
Shallow binding is not appropriate for static-scoped languages with nested
subprograms.
Overloaded Subprograms
An overloaded operator is one that has multiple meanings. The types of its
operands determine the meaning of a particular instance of an overloaded operator.
For example, if the * operator has two floating-point operands in a Java program, it
specifies floating-point multiplication.
But if the same operator has two integer operands, it specifies integer multiplication.
An overloaded subprogram is a subprogram that has the same name as another
subprogram in the same referencing environment.
Every version of an overloaded subprogram must have a unique protocol; that is, it
must be different from the others in the number, order, or types of its parameters,
or in its return if it is a function.
The meaning of a call to an overloaded subprogram is determined by the actual
parameter list.
Users are also allowed to write multiple versions of subprograms with the same
name in Ada, Java, C++, and C#.
Overloaded subprograms that have default parameters can lead to ambiguous
subprogram calls.
void fun(float b = 0.0);
void fun( );

fun( ); // The call is ambiguous and will cause a compilation error.

Generic Subprograms:
A programmer should not need to write four different sort subprograms to sort four
arrays that differ only in element type.
A generic or polymorphic subprogram takes parameters of different types on
different activations.
Overloaded subprograms provide a particular kind of polymorphism called ad hoc
polymorphism.
Parametric polymorphism is provided by a subprogram that takes a generic
parameter that is used in a type expression that describes the types of the
parameters of the subprogram.
Generic Functions in C++
Generic functions in C++ have the descriptive name of template functions.
The following is the C++ version of the generic sort subprogram.
template <class Type>
void generic_sort (Type list [ ], int len) {
int top, bottom;
Type temp;
for (top = 0, top < len –2; top ++)
for (bottom = top + 1; bottom < len – 1; bottom++)
if (list [top] > list [bottom]) {
temp = list [top];
list[top] = list[bottom];
} // end for bottom
} // end for generic
The instantiation of this template function is:
float flt_list [100];

generic_sort (flt_list, 100);

Design Issues for Functions:


Are side effects allowed?
What types of values can be returned?
Functional Side Effects
Because of the problems of side effects of functions that are called in expressions,
parameters to functions should always be in-mode parameters.
Ada functions can have only in-mode formal parameters.
This effectively prevents a function from causing side effects through its parameters
or through aliasing of parameters and globals.
In most languages, however, functions can have either pass-by-value or pass-byreference
parameters, thus allowing functions that cause side effects and aliasing.
Types of Returned Values
C allows any type to be returned by its functions except arrays and functions.
C++ is like C but also allows user-defined types, or classes, to be returned from its
functions.
JavaScript functions can be passed as parameters and returned from functions.

SEMANTICS OF CALL AND RETURN:

The subprogram call and return operations of a language are together called its subprogram
linkage
• General semantics of subprogram calls
Parameter passing methods
Stack-dynamic allocation of local variables
Save the execution status of calling program
Transfer of control and arrange for the return
If subprogram nesting is supported, access to nonlocal variables must be arranged
• General semantics of subprogram returns:
In mode and in out mode parameters must have their values returned
Deallocation of stack-dynamic locals
Restore the execution status
Return control to the caller
• Call Semantics
- Save the execution status of the caller
- Pass the parameters
- Pass the return address to the callee
- Transfer control to the callee
• Return Semantics:
If pass-by-value-result or out mode parameters are used, move the current values of those
parameters to their corresponding actual parameters
If it is a function, move the functional value to a place the caller can get it
Restore the execution status of the caller
Transfer control back to the caller
• Required storage:
Status information, parameters, return address, return value for functions
• Two separate parts: the actual code and the non-code part (local variables and data
that can change)
• The format, or layout, of the non-code part of an executing subprogram is called an
activation record
• An activation record instance is a concrete example of an activation record (the
collection of data for a particular subprogram activation)
• More complex activation record
The compiler must generate code to cause implicit allocation and deallocation of local
variables
Recursion must be supported (adds the possibility of multiple simultaneous activations of
a subprogram)

• The activation record format is static, but its size may be dynamic
• The dynamic link points to the top of an instance of the activation record of the caller
• An activation record instance is dynamically created when a subprogram is called
• Activation record instances reside on the run-time stack
• The Environment Pointer (EP) must be maintained by the run-time system. It always
points at the base of the activation record instance of the currently executing program
unit
• void sub(float total, int part)
• {
• int list[5];
• float sum;
• …
• }

void A(int x) {
int y;
...
C(y);
...
}
void B(float r) {
int s, t;
...
A(s);
...
}
void C(int q) {
...
}
void main() {
float p;
...
B(p);
...
}

IMPLEMENTING SIMPLE SUB PROGRAMS:


We begin with the task of implementing simple subprograms. By “simple” we mean that
subprograms cannot be nested and all local variables are static. Early versions of Fortran were
examples of languages that had this kind of subprograms.

The semantics of a call to a “simple” subprogram requires the following actions:


1.Save the execution status of the current program unit.
2.Compute and pass the parameters.
3.Pass the return address to the called.
4.Transfer control to the called.
The semantics of a return from a simple subprogram requires the following actions:
1.If there are pass-by-value-result or out-mode parameters, the current values of those parameters are
moved to or made available to the corresponding actual parameters.
2.If the subprogram is a function, the functional value is moved to a place accessible to the caller.
3.The execution status of the caller is restored.
4.Control is transferred back to the caller.

The call and return actions require storage for the following:
•Status information about the caller
•Parameters
•Return address
•Return value for functions
•Temporaries used by the code of the subprograms

These, along with the local variables and the subprogram code, form the complete collection
of information a subprogram needs to execute and then return control to the caller.

The question now is the distribution of the call and return actions to the caller and the called.
For simple subprograms, the answer is obvious for most of the parts of the process. The last three
actions of a call clearly must be done by the caller. Saving the execution status of the caller could be
done by either. In the case of the return, the first, third, and fourth actions must be done by the called.
Once again, the restoration of the execution status of the caller could be done by either the caller or
the called. In general, the linkage actions of the called can occur at two different times, either at the
beginning of its execution or at the end. These are sometimes called the prologue and epilogue of the
subprogram linkage. In the case of a simple subprogram, all of the linkage actions of the callee occur
at the end of its execution, so there is no need for a prologue.
A simple subprogram consists of two separate parts: the actual code of the subprogram, which
is constant, and the local variables and data listed previously, which can change when the subprogram
is executed. In the case of simple subprograms, both of these parts have fixed sizes.
The format, or layout, of the noncode part of a subprogram is called an activation record,
because the data it describes are relevant only during the activation, or execution of the subprogram.
The form of an activation record is static. An activation record instance is a concrete example of an
activation record, a collection of data in the form of an activation record.
Because languages with simple subprograms do not support recursion, there can be only one
active version of a given subprogram at a time. Therefore, there can be only a single instance of the
activation record for a subprogram. One possible layout for activation records is shown in Figure
10.1. The saved execution status of the caller is omitted here and in the remainder of this chapter
because it is simple and not relevant to the discussion.
Figure 10.1 Local variables
An activation record for Parameters
simple subprograms
Return address

Because an activation record instance for a “simple” subprogram has


fixed
size, it can be statically allocated. In fact, it could be attached to the code part
of the subprogram.

Figure 10.2 shows a program consisting of a main program and threesubprograms: A, B, and
C. Although the figure shows all the code segmentsseparated from all the activation record instances,
in some cases, the activationrecord instances are attached to their associated code segments.The
construction of the complete program shown in Figure 10.2 is not doneentirely by the compiler. In
fact, if the language allows independent compilation,the four program units—MAIN, A, B, and
C—may have been compiled on differentdays, or even in different years. At the time each unit is
compiled, the machinecode for it, along with a list of references to external subprograms, is written to
a file. The executable program shown in Figure 10.2 is put together by the linker, which is part of the
operating system. (Sometimes linkers are called loaders, linker/loaders, or link editors.) When the
linker is called for a main program, its first task is to find the files that contain the translated
subprograms referenced in that program and load them into memory. Then, the linker must set the
target addresses of all calls to those subprograms in the main program to the entry addresses of those
subprograms. The same must be done for all calls to subprograms in the loaded subprograms and all
calls to library subprograms. In the previous example, the linker was called for MAIN. The linker had
to find the machine code programs for A, B, and C, along with their activation record instances, and
load them into
STACK AND DYNAMIC LOCAL VARIABLES:
A variable is a bundle of six attributes: name, scope, address, lifetime, type, and value.
An attribute may be bound to a variable (or other program entity) at various times. Sebesta mentions:
language design time; language implementation time; compile time; link time; load time; and run
time.
We will just be concerned with a two-way distinction:
Static Binding
Static binding happens at compile time (including link and load time), and usually remains the
same throughout the run of the program.
Dynamic Binding
Dynamic binding happens during run time, and may change during run time.
Name
Most variables have a name. Names were discussed in the previous web page As an example of a
variable without a name, consider this interaction with the Java BeanShell:
bsh % string = "This is a string.";
bsh % set = new HashSet();
bsh % set.add(string);
bsh % print(string);
This is a string.
bsh % print(set);
[This is a string.]
There are two named variables, string and set. The variable string is bound to a word of memory that
contains a reference to a string object. This reference is string's value. The variable set is bound, as its
value, to a reference to an instance of the HashSet class, and that instance includes a word of memory
which contains a copy of the string reference bound, as a value, to string. That element of the HashSet
is as much a variable as string is, but it doesn't have a name.
On the other hand, more than one variable might have the same name. Consider the Java code,
for (int i = 0; i < 10; i++) {
a[i] = i;
}
for (int i = 0; i < 100; i++) {
squares[i] = i*i;
}
The two for loops each have a variable named i, yet they are different variables.
Also, when a subroutine, that has a local variable x, calls itself recursively, each instance of
the subroutine will have a separate variable named x. This issue of subroutine management will be
discussed in more detail later.
The two variables named i in the for loops are statically bound to their names at compile time.
The variables named x, in all recursive activations except the top one, are bound to their names
dynamically, during run time.
When using an interpreted language (such as bash, Erlang, Haskell, the Java BeanShell, Lisp,
Python, or Ruby), variables such as string and set, above, may be created and bound to their names
dynamically, at run time.
Scope
The scope of a variable is the amount of program within which the variable's name refers to it (as
opposed to another variable of the same name). If the "amount" of program is determined spatially,
that is static (sometimes called "lexical") scope. If it is determined temporally, that is dynamic scope.
To discuss scope, we need another 3-way distinction
Local variable
A local variable is a variable declared in the same program subunit (program, subprogram, function,
etc.) in which it is used.
Nonlocal variable
A nonlocal variable is a variable not declared in the same program subunit in which it is used, but not
available to every subunit in the program.
Global variable
A global variable is a variable not declared in the same program subunit in which it is used, and
available to every subunit in the program.
This discussion of scope also applies to names other than variable names, for example, names of
subprograms.
Python displays all the traditional characteristics of static scoping:
<timberlake:CSE305:1:29> cat scope.py
#! /util/bin/python
# Illustration of Nested Scopes
# Stuart C. Shapiro
# February 4, 2005
def A():
print x # global x
def B():
def C():
x = "C's x"
print x # local x
D()
def D():
print x # nonlocal x
x = "B's x"
print x # local x
A()
C()
x = "Global x"
B()
<timberlake:CSE305:1:30> scope.py
B's x
Global x
C's x
B's x
Static and dynamic scope may be clearly compared in Common Lisp and Emacs-Lisp.
Here's an interaction with Common Lisp:
cl-user(1): (setf x 1)
1
cl-user(2): (defun outer (x)
(inner))
outer
cl-user(3): (defun inner ()
x)
inner
cl-user(4): (outer 2)
1
cl-user(5): (inner)
1
Common Lisp's variables are statically scoped. (Although variables may be declared to be
dynamically scoped.) Since x is not local to the function inner, it refers to the variable with the same
name that has most recently been declared looking up the static spatial area of the program. There, the
most recent declaration of x is the global one implicitly declared in the setf expression. So the x of
inner is in the scope of the global x, and they refer to the same variable. However, the x of the
function outer is a formal parameter, and so is in different scope, and so refers to a different variable.
Now here's the apparently same interaction with Emacs-Lisp
(setf x 1)
1
(defun outer (x)
(inner))
outer
(defun inner ()
x)
inner
(outer 2)
2
(inner)
1
Emacs-Lisp is dynamically scoped (like pre-Common Lisp Lisps). Since x is not local to the function
inner, it refers to the variable with the same name that has most recently been declared looking up the
dynamic chain of function calls. When inner was called from outer, that would be outer's x, but when
inner was called from the top-level, that would be the top-level, global x, the one assigned by the setf.
The programming languages that descend from Algol 60 allow blocks where variables may be
declared, giving them smaller scopes than subprograms (methods). This Java for loop

for (int i = 0; i < 10; i++) {


a[i] = i;
}
is an excellent examples of this.
The scope of Prolog variables is limited to a single "clause". There is no lexical nesting. So the
compiler issues a warning when it compiles inner, and the attempt to execute inner causes a run-time
error:
<timberlake:CSE305:1:53> cat scope.pro
:- X is 1, format("Global(?) X is ~d~n", [X]).
outer(X) :-
format("Outer X is ~d~n", [X]),
inner.
inner :-
format("~Inner X is d~n", [X]). % error
:- outer(2).
:- halt.
<timberlake:CSE305:1:54> prolog -l scope.pro
% compiling /projects/shapiro/CSE305/scope.pro...
Global(?) X is 1
* [X] - singleton variables
* Approximate lines: 5-6, file: '/projects/shapiro/CSE305/scope.pro'
Outer X is 2
! Consistency error: [126,73,110,110,101,114,32,88,32,105|...] and user:[_4193] are inconsistent
! format_arguments
! goal: format([126,73,110,110,101,114,32,88,32|...],user:[_4193])
! Approximate lines: 6-8, file: '/projects/shapiro/CSE305/scope.pro'
% compiled /projects/shapiro/CSE305/scope.pro in module user, 0 msec 2304 bytes
Summary
The static scope of a variable is the block in which it is declared, plus all spatially (lexically) enclosed
blocks, except those where it is shadowed by a declaration of another variable with the same name.
Some languages, in some circumstances, include the area of the block in which it is declared that
occurs before the declaration; others don't. Java doesn't allow declaration of a new variable inside the
static scope of another variable of the same name.
The dynamic scope of a variable is the block in which it is declared, plus all dynamically enclosed
blocks, i.e., blocks that are executed while the block in which the variable is declared is still
executing.
Dynamic scope is very difficult to understand and to check for program correctness, since it is
extremely hard to tell, by looking at a program, where any given variable has gotten its value. Most
programming languages use static scoping, although Perl, as well as Common Lisp, allows variables
to be declared to use dynamic scoping.
In general, you should make the scope of any variable be the smallest that is needed. In particular:
declare the for loop index in the for loop itself; and avoid global variables unless absolutely necessary.
See Sebesta for more examples of static and dynamic scoping in block-structured languages.
Address
The address of a variable is also referred to as its l-value, as opposed to the value of the variable,
which is referred to as its r-value. This is from considerations of an assignment statement: x = y,
where the l-value is the address of x, the r-value is the value of y and the r-value is to be stored into
the address at the l-value. Note that the computation of the l-value might be as complicated as the
computation of the r-value, as in
a[<expression>] = <expression>;
Aliases:
Aliases are two variables that share the same address.
Fortran77 has several ways to create aliases. One is by the Equivalence statement:
Program Alias
C Test program for aliases
Integer i,j
Equivalence (i, j)
i=1
10 Print *, '10: i = ', i, ', j = ', j
j=2
20 Print *, '20: i = ', i, ', j = ', j
End
-------------------------------------------------------
<cirrus:Programs:1:124> f77 -o alias.out alias.f
NOTICE: Invoking /opt/SUNWspro/bin/f90 -f77 -ftrap=%none -o alias.out alias.f
alias.f:
MAIN alias:
<cirrus:Programs:1:125> alias.out
10: i = 1, j = 1
20: i = 2, j = 2
The Equivalence statement is deprecated in Fortran90.
Deprecated
"A deprecated element or attribute is one that has been outdated by newer constructs... Deprecated
elements may become obsolete in future versions" [http://www.w3.org/TR/REC-
html40/conform.html]
C also lets you do this, if you know where to look:
/*
* C Alias Program
*
*/
#include <stdio.h>
int main() {
int i, a[3] = {1,2,3}, j;
i = 0;
j = 6;
printf("a = %d, %d, %d, %d, %d, \n", a[-2], a[-1], a[0], a[1], a[2]);
return 0;
}
-------------------------------------------------------
<cirrus:Programs:1:137> gcc -Wall alias.c -o alias.out
<cirrus:Programs:1:138> ./alias.out
a = 0, 6, 1, 2, 3,
This is not a "feature" of C, but results from it not doing range checking on arrays.
There are other ways to create aliases. We will discuss them in later sections of the course.
Clearly, aliasing can lead to programs that are hard to understand and to debug.
Storage Bindings and Lifetime
A variable may be bound to an address in RAM, on the stack, or on the heap. For example,
Fortran77 and earlier Fortrans used neither a stack nor a heap, and so bound all variables to addresses
in RAM;
the versions of the variable n of the following C recursive function are stored on the stack.
int factorial(int n)
{
if (n == 1) return 1;
else return n * factorial(n-1);
}
the HashSet discussed above and its unnamed variables are stored on the heap.
Relevant terms:

Allocation
The process of taking "the memory cell to which a variable is bound ... from a pool of available
memory." [Sebesta, p. 221]
Deallocation
"The process of placing a memory cell that has been unbound from a variable back to the pool of
available memory." [Sebesta, p. 221]
Lifetime:
The time during which the variable is bound to a specific memory location." [Sebesta, p. 221]
Variable categories by lifetime and memory location:
Static Variables
Static variables are bound to memory cells before execution begins, and remain so until program
termination. That is, the lifetime of a static variable is the entire running time of the program.
Fortran77 and earlier versions of Fortran use only static variables. One implication is that recursion is
not possible. This can be demonstrated by a subroutine that keeps count of the number of times it has
been called:
Program Count
C Demonstration of static variables in Fortran.
print *, 'Starting Test Program'
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
End
Subroutine CountingRoutine ()
C Keeps track of the number of times it has been called
C and prints that count each time.
Integer count
Data count/0/
count = count + 1
Print *, 'count = ', count
Return
End
-------------------------------------------------------
<cirrus:Programs:1:152> f77 -o count.fout count.f
NOTICE: Invoking /opt/SUNWspro/bin/f90 -f77 -ftrap=%none -o count.fout count.f
count.f:
MAIN count:
countingroutine:

<cirrus:Programs:1:153> count.fout
Starting Test Program
count = 1
count = 2
count = 3
count = 4
count = 5
Notice that, even though count is a local variable of CountingRoutine, its lifetime exceeds the running
time of CountingRoutine.
C can achieve this effect by declaring a variable to be static:
/*
* Count
* Stuart C. Shapiro
*
* This program demonstrates static variables
* with a function that counts the number of times that it is called.
*
*/

#include <stdio.h>

void counting_function() {
/* Prints the number of times that it has been called. */
static int count = 0;
printf("count = %d\n", ++count);
}

int main() {
/* Demonstrates counting_function by calling it 5 times. */
counting_function(); counting_function(); counting_function();
counting_function(); counting_function();
return 0;
}
-------------------------------------------------------
<cirrus:Programs:1:140> gcc -Wall count.c
<cirrus:Programs:1:141> ./a.out
count = 1
count = 2
count = 3
count = 4
count = 5
This can also be done in Java:
/**
* Counter.java
*
*
* Created: Mon Sep 15 16:47:41 2003
*
* @author Stuart C. Shapiro
*/
public class Counter {
public static int count;

public Counter (){


}
/* Prints the number of times that it has been called.
*/
public static void counting_function() {
System.out.println("count = " + ++count);
}
/* Demonstrates counting_function by calling it 5 times. */
public static void main (String[] args) {
counting_function(); counting_function(); counting_function();
counting_function(); counting_function();
} // end of main ()
}// Counter
-------------------------------------------------------
<cirrus:Programs:1:142> javac Counter.java
<cirrus:Programs:1:143> java Counter
count = 1
count = 2
count = 3
count = 4
count = 5
Explicit Heap-Dynamic Variables
Explicit heap-dynamic variables are those nameless variables allocated on the heap for dynamic data
structures or for instances of objects in OO programming languages. In Java or C++, they are
allocated by the new operator. For example, this Java BeanShell statement

bsh % set = new HashSet();


allocates memory cells on the heap to hold the instance variables of a HashSet, binds the nameless
variables to those cells, and returns a reference (pointer) to them to be stored in the stack-dynamic
variable set.
In C, the allocation operator is the function malloc(size), which takes an argument specifying the
amount of memory required, and returns a pointer to that area of heap memory.

Heap memory must be used for any dynamically allocated object or data structure that can be
allocated in a subroutine and then have a pointer (reference) to it assigned to a variable which is
outside the dynamic scope of the subroutine, so that its lifetime must extend to the time after the
subroutine terminates and deallocates its stack memory. Consider the Java program,

import java.util.*;
public class HeapDemo {
public static HashSet singleton(Object obj) {
HashSet set;
set = new HashSet();
set.add(obj);
return set;
}
public static void main (String[] args) {
HashSet myset;
myset = singleton("element");
System.out.println(myset);
myset = singleton("another");
System.out.println(myset);
}
}// HeapDemo
-------------------------------------------------------
<cirrus:Programs:2:101> javac HeapDemo.java
<cirrus:Programs:2:102> java HeapDemo
[element]
[another]
Although the memory for the HashSet is allocated in the singleton method, it cannot be allocated on
the stack, because it must survive the termination of singleton.
Heap memory should be returned to the heap when it is no longer needed (about to be no longer
reachable from any named variable), like the HashSet [element] was no longer needed (reachable)
after myset was reassigned above. Otherwise, a program that runs long enough might use up the heap
and abnormally terminate. (The process of heap memory becoming increasingly unreachable but
unavailable for reallocation is called "memory leakage".) In C and C++, heap memory must be
explicitly deallocated with the operator free(p) or delete p, respectively, where p is a pointer to the
object or data structure whose memory is no longer needed.

Requiring the programmer to explicitly deallocate heap memory allows for these programmer
mistakes:

failing to deallocate unreachable storage, resulting in memory leakage;


deallocating memory that is still reachable, the "dangling pointer" problem, which is discussed again
in Chapter 6.
A more reliable idea is for the programming system itself to deallocate unreachable heap memory, a
process called "automatic garbage collection", which will be discussed again in Chapter 6. Lisp was
the first programming language to perform automatic garbage collection. Some other languages that
do automatic garbage collection are Erlang, Haskell, Java, Perl, Python, and Ruby.

Implicit Heap-Dynamic Variables


An implicit heap-dynamic variable is like an explicit heap-dynamic variable, but is created without an
explicit allocation operator. Sebesta gives as an example the JavaScript statement, list = [10.2, 3.5],
where the variable storing the two-element array (not the variable list) is an implicit heap-dynamic
variable. I am a little skeptical about this category.
Closures
A closure is a function that uses a nonlocal variable and that can be called by a function outside the
scope of the variable. Here's a Common Lisp example:
cl-user(5): (let ((count 0))
(defun countingFunction ()
(incf count)))
countingFunction
cl-user(6): (countingFunction)
1
cl-user(7): (countingFunction)
2
cl-user(8): (countingFunction)
3
cl-user(9): (countingFunction)
4
cl-user(10): count
Error: Attempt to take the value of the unbound variable `count'.
[condition type: unbound-variable]
...
[1] cl-user(11): :res
cl-user(12): (countingFunction)
5
Notice that count must be a heap-dynamic variable, because its lifetime exceeds the execution time of
the block in which it is declared.
Type
The type of a variable specifies;
how the bits in its memory cell are to be interpreted;
the range of values the variable can have;
the operations that are defined for those values;
which operations are to be used for overloaded operators, such as +, with the variable as operand.
Static Typing
In static typing, the compiler determines the type of every variable and expression, and therefore the
operation to use for overloaded operators, for example the operation denoted by + in the statement x =
y + z. Once compiled, the information used by the compiler for typing needn't be retained. Often, the
names of variables are not retained after compilation, so that symbolic debugging of a running
program cannot be done.
Implicit Static Typing
In implicit static typing, the compiler determines the type of a variable by the variable's name. For
example,
in Perl, @
is an array
a name starting with $ is a scalar (a number or string)
% is a hash
and in Fortran,
a name starting with I, i, J, j, K, k, L, l, M, m, N, n is an integer
a name starting with anything else is a real
Explicit Static Typing
In explicit static typing, the most common in modern languages, the type of each variable is given to
the compiler by a declaration statement, which is not an executable statement---it is used by the
compiler, rather than being converted into executable code.
In Fortran, an explicit type declaration can override the implicit naming convention, which could lead
to confusion.

Inferred Static Typing


The ML compiler determines the type of each variable by inferring it from various evidence, though it
can be declared also.
<timberlake:CSE305:2:109> sml
Standard ML of New Jersey v110.69 [built: Thu May 28 09:54:29 2009]
- fun reciprocal(x) = 1.0 / x;
val reciprocal = fn : real -> real
- fun bad(y) = reciprocal(y) + 2 * y;
stdIn:2.14-2.35 Error: operator and operand don't agree [literal]
operator domain: int * int
operand: int * real
in expression:
2*y
The explanation of this error message is
2 is of type int.
Since * requires both its operands to be of the same type and 2 is an int, y should be an int.
But y is the argument of the function reciprocal.
When reciprocal was defined, its formal parameter was used in an expression that requires it to be a
real.
Therefore the actual argument of reciprocal, namely y must be real.
That is a type conflict, and a compiler error.
Haskell also uses static typing and does type inference.
Dynamic Typing:
It should be that in dynamic typing, the variable is bound to a type during run-time. Sebesta says, "the
variable is bound to a type when it is assigned a value in an assignment statement" [p. 198].
However, it seems better to think of such languages has having typed values, rather than typed
variables. For example, the Python Reference Manual says "Objects are Python's abstraction for
data...Every object has an identity, a type and a value" [Sect. 3.1], and the ANSI Common Lisp
standard says that "Objects, not variables, have types" [ANSI Common Lisp, Section 4.1]. Consider
this example:
cl-user(1): (setf x 33.72
y 7.9)

cl-user(2): (print (gcd x y))


Error: `33.72' is not of the expected type `integer'
[condition type: type-error]
In this example, gcd requires its arguments to be integers, and a type error results. Yet the type error is
about 33.72, not about x.
Although Java uses static typing, it also has types associated with values:
bsh % list = new LinkedList();
bsh % list.add("A string");
bsh % list.add(new HashSet());
bsh % print(list.getFirst().getClass());
class java.lang.String
bsh % print(list.get(1).getClass());
class java.util.HashSet
Notice that in the Java expression referenceVariable.method(), the static type of referenceVariable
must have the method() defined for it, or a compile-time error will be issued. However, the actual
class of the dynamic value of referenceVariable will be used to choose the particular details of the
method(), as long as that class is a subclass of the static class of referenceVariable.

For example, let ClassA be a class in which methodA() is defined, let ClassA1, ClassA2, and ClassA3
extend ClassA and specialize methodA(), and let varA be a variable declared to be of type ClassA.
UML diagram by Dan Schlegel
The expression varA.methodA() is syntactically legal regardless of whether the current value of varA
is an object of type ClassA, ClassA1, ClassA2, or ClassA3, but the value of varA.getClass() will
determine the specific version of methodA() used.
On the other hand, if varB is declared to be of some superclass of ClassA for which methodA() is not
defined, varB.methodA() will produce a compiler error, unless a cast is used, such as
((ClassA2)varB).methodA().

Languages with typed values include Common Lisp, Haskell, Java, JavaScript, Ruby and Python.

Usually, in languages with typed values, the programmer may write code to test the types of values
and give reasonable error messages if they are not what was expected, but, without doing this, a type
error might only be caught many levels of function calls below where the error actually occurred.
Static type-checking generally makes program debugging easier.

Value
The value of a variable, also called its r-value, is the contents of the memory cell at the variable's
address.
This might lead to confusion with pointer or reference variables.
For example, after the assignment set = new HashSet();, above, should we say that the value of set is a
HashSet or a reference to a HashSet? The latter is the more careful way to speak; the former is more
informal. We will discuss this more when we discuss pointer types in Chapter 6.
Now we will discuss when a variable is first bound to a value, and whether its value binding is
allowed to change.
A formal Parameter is initially bound to a value when its subprogram is called. We will discuss this in
Chapter 9.
The remainder of this discussion will concern variables that are not parameters---global and local
variables.
When a variable is bound to an address (memory cell), its value might be whatever bit settings were
left in that cell, interpreted according to the variable's type, for example this C program:

#include <stdio.h>
int main() {
int x;
double y;
printf("x = %d y = %e\n", x, y);
return 0;
}
-------------------------------------------------------
<cirrus:Programs:1:103> gcc -Wall leftover.c -o leftover.out
<cirrus:Programs:1:104> ./leftover.out
x = -4264396 y = 8.485876e-314
or it might be initialized, either to a default value, or to an value explicitly specified in a declaration.
Variable Initialization might be done at compile-time, or at run-time.
If variable initialization is done at compile-time, the initialization expression will usually be limited.
NESTED SUBPROGRAMS:
•Some non-C-based static-scoped languages (e.g., Fortran 95, Ada, JavaScript)
use stack-dynamic local variables and allow subprograms to be nested
•All variables that can be non-locally accessed reside in some activation record
instance of enclosing scopes in the stack
•A reference to a non-locally variable in a static-scoped language with nested
subprograms requires a two step access process:
1.Find the correct activation record instance
2.Determine the correct offset within that activation record instance

Static Scoping of Nested subprograms

 I

The static link in an activation record instance for subprogram A points to the bottom of the
activation record instances of A's static parent

program MAIN_2; //Example Pascal Program


var X : integer;
procedure BIGSUB; Activation Records at Position 1
var A, B, C : integer;
procedure SUB1;
var A, D : integer;
begin { SUB1 }
A := B + C; <----------------1
end; { SUB1 }
procedure SUB2(X : integer);
var B, E : integer;
procedure SUB3;
var C, E : integer;
begin { SUB3 }
SUB1;
E := B + A: <-------------2
end; { SUB3 }
begin { SUB2 }
SUB3;
A := D + E; <----------------3
end; { SUB2 }
begin { BIGSUB }
SUB2(7);
end; { BIGSUB }
begin
BIGSUB;
end; { MAIN_2 }
•Call sequence for MAIN_2 ?
•A static chain is a chain of static links that connects certain activation record
instances
•The static chain from an activation record instance connects it to all of its static
ancestors
Sub3 Sub2 BigSub Main2
Finding the correct activity record instance of a nonlocal variable using static links is
relatively straightforward.
Point 1:
To access nonlocal variable B? C?
After Sub1 complete its execution, the activation record instance for Sub1 is removed from
the stack, and control return to Sub3.
Point 2: to access E? B? A?
Blocks:
•Blocks are user-specified local scopes for variables. It is legal to declare variables
within blocks contained within other blocks.
• An example in C
void SquareTable(int lower, int upper){
int n;
for (n = lower; n <= upper; n++) {
int square;
square = n * n;
printf("%8d%8d\n", n, square); }
}
(a) When does square get allocated and deallocated?
The memory is allocated and deallocated on each pass through the inner block.
(b) How should the memory diagram be drawn?

Implementing Blocks
1.Treat blocks as parameter-less subprograms that are always called from the same
location.
– Every block has an activation record; an instance is created every time the block is
executed
void main(){
int x, y, z;
while ( … ){
int a, b, c;
……
while ( …){
int d, e;
…… }
}
while ( … ) {
int f, g;
…… } ……}
2. Since the maximum storage
required for a block can be
statically determined, this amount
of space can be allocated after the
local variables in the activation record
Implementing Dynamic Scoping:

Scoping itself is how you search for a variable with a given name. A variable has
a scope which is the whole area in which that variable can be accessed by name. If
there is a reference to a variable "a" then how does the compiler or interpreter find
it?
In lexical scoping (and if you're the interpreter), you search in the local function
(the function which is running now), then you search in the function (or scope) in
which that function was defined, then you search in the function (scope) in
which that function was defined, and so forth. "Lexical" here refers to text, in that
you can find out what variable is being referred to by looking at the nesting of
scopes in the program text.
In dynamic scoping, by contrast, you search in the local function first, then you
search in the function that called the local function, then you search in the function
that called that function, and so on, up the call stack. "Dynamic" refers
to change, in that the call stack can be different every time a given function is
called, and so the function might hit different variables depending on where it is
called from.
Dynamic scoping is useful as a substitute for globally scoped variables. A function
can say "let current_numeric_base = 16; call other functions;" and the other
functions will all print in hexadecimal. Then when they return, and the base-setting
function returns, the base will return to whatever it was.
It can also have a use similar to a ContextObject, as in JavaServlets and EJB APIs,
holding state for a series of subroutines.
The two most popular methods for
implementing DynamicScoping are DeepBinding and ShallowBinding.

See GuySteele and GeraldSussman's paper TheArtOfTheInterpreter for discussion


of static vs dynamic scoping and more.

[The following is somewhat specific to LispSchemeDifferences and the use of


dynamic variables in CommonLisp.]
What does dynamic scoping do for *standard-output* that a temporary assignment
to a global does not? I presume that it would be simple enough to write a macro
that saves the old value, assigns a new one, executes a form, and restores the old
value before returning the value of the form? Wouldn't such a macro simply be
implementing dynamic scoping in terms of lexical scoping?
GuySteele's original papers on the SchemeLanguage papers showed that
dynamically scoped variables are equivalent to the controlled use of global
variables. So yes, writing a macro to save, change and restore arbitrary global
variables actually implements dynamic scoping. Global variables don't really have
lexical scope, however, so it's not "in terms of" lexical scoping. Unfortunately,
dynamic scoping (along with global variables) begins to break down in multi-
threaded situations.
If you just save the variable, assign to it, and restore it, it won't be thread safe, and
it will require a special binding construct. Real SpecialVariables work with any
binding construct: LET, MULTIPLE-VALUE-BIND, WITH-OPEN-FILE, etc; the
special nature of the binding is remembered as a property of the symbol and
applied accordingly. And in Lisp implementations that support threads, the
bindings are thread-specific! Implementing thread-specific storage is not trivial. If
you assign to *standard-output*, you have a race condition. Assignment is not
binding.
I agree with AlainPicard that sometimes dynamic scope is what you want. (Though
I don't want it as often as he does, since global *PRINT-BASE* in particular has
caused me problems. I wish the print base were an attribute of streams, instead of a
global variable.) But SunirShah's original proposal [on LispSchemeDifferences]
was to use dynamic scoping to replace all lexical scoping. That would be a very
bad idea, most of all because it would replace the clean semantics of closures with
semantics so twisted that closures couldn't be used in non-trivial code without
creating horrible maintenance problems. -- BillNewman
Bill, *PRINT-BASE* et al are special, not global. There is a world of difference. --
ap
Yeah. I think what Bill is talking about, though (and he can refactor his words and
delete my comment if I'm wrong) is that *print-base* et al apply to output
on every stream, instead of being individually settable on each. For example, I
have an application which uses the printer to serialize data into a database: suppose
that the debugger binds *print-length* to something non-nil for human readability,
then the programmer calls my SAVE-OBJECT function on a list from the break
loop, so it saves something which will later be unREADable. Basically, SAVE-
OBJECT has to be very careful to rebind all the printer-related variables to known-
good values.
This is exactly why your application should re-bind the required variable at a low
point in the call stack, rather than depend on what may accidentally be in
existence. The WITH-STANDARD-IO-SYNTAX is provided specifically for this
purpose. -- ap

I find that dynamic scoping makes spaghetti code out of modular code. Everything
depends on order of execution, and it quickly becomes impossible to understand
what's going on without a debugger open. Just an opinion, however... --Anthony
Lexical scoping is easier to program with, and is necessary for safe closures,
callbacks, CallWithCurrentContinuation, HigherOrderFunctions etc.
Consider variables like *standard-output*, *print-base*, *break-on-signals*, etc...
Dynamic scoping has the potentiality of turning code into a mess, but, hey, all
powerful constructs are like that. It's certainly a much less dangerous feature than,
say, call-cc, which is so powerful it prevents a reasonable implementation of
UNWIND-PROTECT. -- AlainPicard
Hmm. I was under the impression that 'dynamic-wind' solved these issues,
although it is a fairly recent addition if I'm not mistaken. -- WilliamUnderwood

Richard Stallman makes a good case for dynamic scoping in his paper on
emacs: http://www.gnu.org/software/emacs/emacs-paper.html#SEC17
Incidentally, emacs ships with macros that support lexical scoping in emacs lisp
(emacs lisp variabels are dynamically scoped by default, though lexical scoping
turned on on a per-file basis is supported since version 24) This would suggest that
dynamic scoping is the more general of the two - or is it possible to implement
dynamic scoping in a lexically scoped language in a similar way?
Possibly. A trivial, but non-threasafe, implementation follows in pseudocode
(because I don't know lisp) which is based on lisp and perl six and should probably
be rewritten. Also, since Assembly has neither, yet is used to implement both
several times, your argument may be irrelevant.
define-macro-variable dynamic-names = []
defmacro "defdyn" (a : Name, (optional "=", value = NULL))
defvar code = q:code{
defvar #{a}-dynamic-list = ["value"];
}
dynamic-names += a
(type-of a) = Dynamic[(err (type-of value) (infer-type-of-slot a))]
return code
defmacro [X] (a : Slot[Dynamic[X]])
return {(head #{a}-dynamic-list)}
defmacro "enter-dynamic-scope"
return (reduce dynamic-names q:code{} (lambda (a : Code, b : Name)
a + q:code { #{b}-dynamic-list = (cons (head #{b}-dynamic-list)
#{b}-dynamic-list);}))
defmacro "leave-dynamic-scope"
return (reduce dynamic-names q:code{} (lambda (a : Code, b : Name)
a + q:code { #{b}-dynamic-list = (tail #{b}-dynamic-list)}))

You might also like