|
|
|
|||||||||||||||
|
|
Name scopingWe now consider the question of "what declaration corresponds to a reference to a name from somewhere in the program?" and we begin by considering how name binding is typically implemented for a block-structured language such as C or ML.The most important concepts in this section of the class is fully understanding the notions of scope including both static scope and dynamic scope, as well as the related notions of lifetime and referencing environment. Definition: the scope of a declaration is all the locations in the program where the name(s) bound in that declaration are visible (that is, they can be referenced). Informal type analysis of this English definition can help here. Note that scope is a function from declarations to sets of program locations. The referencing environment of a location is all of the declarations that are visible at that location. The informal type of referencing environment is a function from program locations to sets of declarations. Do not confuse scope with lifetime which is the period of time from the allocation of space for a binding to its de-allocation. BlocksWe consider these ideas first in the context of nested blocks.
{ int x = 3;
{ int x = 5;
{ int y = x;
} } }
In this example the lifetime of each variable is the time from when its block
is entered until it is exited. However, the scope of the first declaration of
x does not include the declaration of y because the second declaration of x
hides or shadows the first one. Another way of saying this is that
there is a hole in the scope of the first declaration of x.
What is going on here: conceptually, as each block is entered a new activation record is placed on the program stack. An activation record contains locations for all of the bindings introduced in the block along with a pointer to the activation record of the previous block. In ML each fun and val declaration introduces a new, nested block. (Note that the book is incorrect on this point --bottom of p. 167 and top of p. 168; to see this for yourself try the following: fun f x = g x; fun g y = f y;If you try to define mutually recursive functions like this you will get an error in the declaration of f saying that g is undeclared. If you want to declare mutually recursive functions use fun f x = g x and g y = f x; Static and Dynamic ScopeSo now we return to the basic question: which of potentially many possible declarations is referred to when an identifier is used in a program? Two possible answers seem to make sense:
val y = 4 fun g x = x+y fun f x = let val y = 7 in g x endIn the function g, x is called a local reference because
it is declared as a parameter to g. On the other hand, y is declared outside
g and this is called a non-local reference or global reference. I prefer the
non-local terminology, reserving the term global for declarations at the outermost score.
Both terms are in common use so its worth knowing what they mean.
In the example, using static scoping (f 10) is 14, but under dynamic scoping (f 10) would be 17. ML, Java, C++ and C all use static scoping, though for C its pretty simple because there are no nested functions. For Java and C++ nested classes are similar to nested functions and follow similar rules. Function implementationCarefully study the figures in Chapter 7 to see how a stack is laid out with access links in each function activation record to allow static scoping. Remeber that although we have drawn the activation records with the names of bindings in them this isn't necessary when using static scoping: the compiler can statically determine how many access links have to be followed and the offset in the activation record for any identifier reference.Programming example for static scopingSo you may ask, how do we take advantage of static scoping when programming? Here is a small example. The ML built-in functionList.filter has type
('a->bool)->'a list->'a list. It takes a function and a list and returns a
sublist of elements on which the function returns true. We can use static scoping to
help us implement a function that counts the number of elements in a list equal to some
value.
fun count x l = let fun eqx y = (x=y) in len (List.filter eqx l) endNotice how eqx here is a function of one argument that invokes the = function on two arguments, one being its own argument y and the other being the non-local argument x bound as a parameter of count. For simple functions like eqx it is often convenient to not even give them a name as in the following equivalent code: fun count x l = len (List.filter (fn y => (x=y)) l)which is concise and quite readable (with practice). |
||||||||||||||
| (c) 2003 Curtis Dyreson, (c) 2004 Carl H. Hauser E-mail questions or comments to Prof. Carl Hauser | ||||||||||||||||