quinta-feira, 7 de agosto de 2008

Workaround for the Scoping Gotcha

My colleague pointed out to me today that you can get around the scoping issue I mentioned by writing your method like this :

Method f() {
set x = 6
set m = "write !,"_x ; difference
xecute m

What's the difference? Instead of including the name of the variable x inside the string, you concatenate the value of the local variable x with the string at run-time, before the string is finally evaluated. Because the value of x is resolved before the xecute, things work as you'd expect.

That's true. But I still regard this as a serious bug. An equivalent python program :

class A :
def f(self) :
x = 6
m = "print x"
exec m

x = 9
a = A()

prints what you'd expect : the number 6.

What's really going on to cause this issue in Caché? My guess is that the OO layer in Caché is compiled down to plain Caché .int routines which don't have a significantly different semantics or scoping rules to Caché ObjectScript. In order to get the effect of the private world inside the object, at compile time the variable x is renamed to some object-specific equivalent. Of course, this renaming affects the real references to x, but not the string assigned to m.

When the VM then tries to xecute the string, it encounters the name x but finds no binding in the local execution frame and so, according to COS's dynamic scoping rules, has to look down the stack for a frame which does have a binding, the place from which we call y.f(), where x is bound to 9.

Of course, this hypothesis might be completely wrong. Expert correction is welcome.
Don't be confused. This is NOT Caché ObjectScript. :-)

quarta-feira, 6 de agosto de 2008

Xecute Scoping

A nasty gotcha in scoping when you try to use Caché's "interpret a string containing code" command xecute inside an object.

You can execute (ie. eval) a string containing a piece of Caché ObjectScript with xecute like this :

set m = "write 2+2"
xecute m

xecute only works on statements, not expressions. But you can use the alternative @ for that. Eg.

set m = "2+2"
write @m

However, if you try to use this inside a class definition, something odd happens.

When a class is compiled, the string which contains code is obviously not affected by the pre-processor. But it seems that when the method is run and the string xecuted, the variable names in the string don't get bound to the scope within the method. Instead they get their values from the global frame or somewhere else entirely.

Look at the following example.

A class :

Class MY.ScopeGotcha Extends %Persistent [ ClassType = persistent, ProcedureBlock ]

Method f() {
set x = 6
set m = "write !,x"
xecute m


Now try running it from the terminal like this :

set x = 9
set y = ##class(MY.ScopeGotcha).%New()
do y.f()

Guess what it prints ... :-)