Context binding with derived class methods used in callbacks

I have an interesting challenge to overcome when attempting to use the base class method of an overridden method of a derived class in a callback. The following simplified code illustrates the problem :

`
class Simple
{
_prop1 = null;
_prop2 = null;

constructor(prop1,prop2)
{
_prop1 = prop1;
_prop2 = prop2;
}

function DoSomething()
{
server.log("prop1 = " + _prop1 + "/prop2 = " + _prop2);
}
}

class LessSimple extends Simple
{
_prop3 = null;
_base = null;

constructor(prop1, prop2,prop3)
{
base.constructor(prop1,prop2);
_prop3 = prop3;
_base = base;
server.log("prop1 = " + _prop1 + "/prop2 = " + _prop2);
}

// override of function
function DoSomething()
{
imp.wakeup(5,function(){
_base.DoSomething();
}.bindenv(this))

}
}

test <- LessSimple(1,2,3);
test.DoSomething();`

Result:
2014-10-15 00:18:14 UTC+2 [Status] Downloading new code; 3.16% program storage used
2014-10-15 00:18:14 UTC+2 [Device] prop1 = 1/prop2 = 2
2014-10-15 00:18:19 UTC+2 [Device] prop1 = (null : 0x0)/prop2 = (null : 0x0)

There’s 2 problems:

  1. the index ‘base’ obviously exists in the instance of the class LessSimple, but it does not exist in the imp.wakeup callback function. Even when explicitely attaching the context of the LessSimple instance by using bindenv(this). This can be solved by declaring a LessSimple property ‘_base’ that is assigned with the reference of base during construction (as done in the example above).
  2. when attempting to call the original method of the base class (which is overridden in the derived class) in the imp.wakeup callback, the context of the callback, provided by bindenv(this) does not contain initialised versions of the properties of the base class, while the context of the LessSimple instance does contain them.
    Executing the above does not print “prop1 = 1 /prop2 = 2”, for the call to _base.DoSomething() in the callback but “prop1 = (null : 0x0)/prop2 = (null : 0x0)” indicating that the properties of the base class are not initialised, despite the fact that the base constructor is called in the derived class constructor. It seems like the properties of the base class are not part of the context of the derived class instance that gets passed to the callback…

Questions:

  1. for problem 1, is there a more elegant way to be able to use the base class methods in a callback ?
  2. what is the right code structure (with which context passing) that makes sure that when calling the base class method in a callback, it also gets access to the initialised instances of the base class properties ?

After some experimentation, solution for (2) can be found by changing the overriden function to :

function DoSomething() { imp.wakeup(5,function(){ _base.DoSomething.call(this); }.bindenv(this)) }

I’m still interested to understand why ‘base’ can’t be used in the callback (probably because it’s a language keyword and not an index of the instance) and why the use of .bindenv(this) attached to the anonymous callback function isn’t enough to have the same effect as when using the explicit call function. Logically it should be.

Anyone of the Squirrel guru’s have a clue ?? Can it be that a context that is ‘imported’ does not implicitely gets attached when making one call deeper ? That would mean there’s besides the imported context still another original one existing that implicitely gets attached instead. Just guessing here to understand the finer details of how Squirrel works internally.

In Squirrel, ‘base’ doesn’t refer to an instance of the base class, but to the class declaration itself. (If in your original code you add server.log(typeof(this)); to DoSomething(), then you see “class” and not, as it should be, “instance”.)

So in fact _base.DoSomething.call(this); is the right answer – fetch the DoSomething method from the base class, but call it with “this” as its instance.

As for why you need “_base” and can’t just use “base” inside your wakeup handler: for reasons that escape me, “base” is implemented to return the base class not of the context object as you (and I) expected, but of the class the function was originally declared in. Most of the time that’s the same thing, but your wakeup handler is declared as a free function, not a class method, so inside that handler function there is no “base”.

Not defending any of this – just reporting it!

Peter

Thanks Peter.

Can I conclude that in an anonymous function, which received a context through .bindenv(this), this context is not added implicitely to further function calls made in that anonymous function ? If it would be, there would be no need for .call(this) added explicitely to such function call, no ?

Anyway, investigating it (and in the process of doing so being forced to reread all the available material on context binding) greatly enhanced my understanding of how Squirrel works, so it wasn’t a waste of time. On the contrary.
As you can expect, the code where I need this is far more complex (and powerful) then this example so I’m glad there is a solution alltogether :slight_smile: There’s of course alternative ways, but I like it when I can do my coding in a beautiful and efficient manner …

The context is always added implicitly to further function calls – except for calls made using the “X.Y()” syntax, which always get X passed as the context object.

So this works:
function DoSomething() { imp.wakeup(5,function(){ local f = _base.DoSomething; f(); }.bindenv(this)) } }
as does this:
function DoSomething() { imp.wakeup(5,function(){ (_base.DoSomething)(); }.bindenv(this)) } }
even though this doesn’t:
function DoSomething() { imp.wakeup(5,function(){ _base.DoSomething(); }.bindenv(this)) } }

Squirrel, you crazy :frowning:

So even our quite new and lovingly-prepared context-binding documentation turns out not to tell the whole story. The whole story is this:

  1. If you’ve used bindenv(e1), the called function gets e1 as its context-object
  2. Otherwise, if you use call(e2), the called function gets e2 as its context-object
  3. Otherwise, if you use e3.f(), the called function gets e3 as its context-object
  4. Otherwise, the called function gets the caller’s context-object

Peter

`local e1 = { x=1 };
local e2 = { x=2, f=function() { server.log(x); } };
x <- 3;
local e4 = { x=4, f=e2.f.bindenv(e1) };
local e5 = { x=5, f=e2.f };
local e6 = { x=6 };

e2.f(); // logs 2 (Rule 3)
(e2.f)(); // logs 3 (Rule 4)
e4.f(); // logs 1 (Rule 1)
(e4.f)(); // logs 1 (Rule 1)
e5.f(); // logs 5 (Rule 3)
(e5.f)(); // logs 3 (Rule 4)
e2.f.call(e6); // logs 6 (Rule 2)
e4.f.call(e6); // logs 1 (Rule 1)
`

Peter

Unbelievable what you can do with this language.
I have to switch back and forth between C (host ARM), Squirrel (eImp) and C# (app with Xamarin). What takes 2 mins to write in Sq takes 10 mins in C# and an hour in C :slight_smile:

Well that’s good to hear. You’ve definitely found a couple of Squirrel oddities here, but I think it’s still the case that Squirrel is mostly a very straightforward language.

Peter