[Next] [Previous] [Up] [Top] [Contents] [Index]

2.7 Method Lookup

2.7.7 Method Lookup and Lexical Scoping

Since methods may be declared both at the top level and nested inside of methods, method lookup must take into account not only which methods are more specialized than which others but also which methods are defined in more deeply-nested scopes. The interaction between lexical scoping and inheritance becomes even more significant in the presence of modules as described in section 5.

The view of lexically-nested methods in Cecil is that nested methods extend the inheritance graph defined in the enclosing scope, rather than override it. We call this "porous" lexical scoping of methods, since the enclosing scope filters through into the nested scope. When performing method lookup for a message within some nested scope, the set of methods under consideration are those declared in the current scope plus any methods defined in lexically-enclosing scopes. If a local method has the same name, number of arguments, and argument specializers as a method defined in an enclosing scope, then the local method shadows (replaces) the method in the enclosing scope. Additionally, any object declarations or object extension declarations in the local scope are added to those declarations and extensions defined in enclosing scopes. Once this augmented inheritance graph is constructed, method lookup proceeds as before without reference to the scope in which some object or method is defined.

Other languages, such as BETA [Kristensen et al. 87], take the opposite approach, searching for a matching method in one scope before proceeding to the enclosing scope. If a matching method is found in one scope, it is selected even if a more specialized method is defined in an enclosing scope. More experience is needed to judge which of these alternatives is preferable. Cecil's approach gets some advantage by distinguishing variable references, which always respect only the lexical scope, from field references, which always are treated as message sends and primarily respect inheritance links. BETA uses the same syntax to access both global variables and inherited instance variables, making the semantics of the construct somewhat more complicated.

Nested methods can be used to achieve the effect of a typecase statement as found in other languages, including Trellis and Modula-3 [Nelson 91, Harbison 92]. For example, to test the implementation of an object, executing different code for each case, the programmer could write something like the following:

method test(x) {
	method typecase(z@obj1) { (-- code for case where x inherits from obj1 --) }
	method typecase(z@obj2) { (-- code for case where x inherits from obj2 --) }
	method typecase(z@obj3) { (-- code for case where x inherits from obj3 --) }
	method typecase(z)      { (-- code for default case --) }
	typecase(x);
}

In the example, obj1, obj2, and obj3 may be related in the inheritance hierarchy, in which case the most-specific case will be chosen. If no case applies or no one case is most specific, then a "message not understood" or an "ambiguous message" error will result. These results fall out of the semantics of method lookup. By nesting the typecase methods inside the calling method, the method bodies can access other variables in the calling method through lexical scoping, plus the scope of the temporary typecase methods is limited to that particular method invocation. Eiffel's reverse assignment attempt and Modula-3's NARROW operation can be handled similarly.