Understand And Use this, call() And bindenv()
Every time Squirrel calls a function, it passes a value into a special initial parameter called this. This is an implicit parameter, so you don’t need to add it yourself — indeed, you can’t do so, if you call functions in the usual way. But this is a hidden first parameter passed to every Squirrel function call.
What is being passed is a reference to the ‘context object’ or ‘environment object’. This is the object to which the function belongs, either the main program itself, an instance of a class, or a table in which the function has been stored. The context object provides the function with access to all the other functions and variables defined within the scope of the called function. This access is known as ‘capturing’ or ‘closing over’ those outer variables and functions. It is done by keeping a reference to those local variables.
How does this work? Let’s say you define a couple of functions in your agent or device code as follows:
function aFunction(integer1, integer2) {
// . . .
}
function anotherFunction(integer3, integer4) {
// . . .
}
and call the first of these at the start of the program proper:
aFunction(10, 20);
what you are implicitly calling is:
aFunction(this, 10, 20);
In this example, the context object passed as a reference into this is the main part of program itself, ie. Squirrel’s global (root) table, which can be retrieved in code using the Squirrel function getroottable(). Passing in the root table this way allows aFunction() to make use of this’ other functions using dot syntax. How does this work? aFunction() might contain the line:
this.anotherFunction(integer1, integer2);
This calls the main context’s anotherFunction() and passes the two values originally passed into aFunction() when it was called.
In fact, Squirrel automatically checks this for a function name if the name hasn’t been defined by the current function. Because Squirrel automatically searches the passed context object, it’s not necessary to add this to function and variable names. Instead, we would write the above line as:
anotherFunction(integer1, integer2);
Squirrel’s automatic searching of the context object is particularly important when it comes to working with classes and their instances. If, for example, you declare a class called MyClass which contains a function called newFunction(), you instantiate that class as follows:
local myInstance = MyClass();
and call the function thus:
myInstance.newFunction();
In this case, newFunction()’s this variable contains a reference to myInstance. All instances pass themselves as a reference to their methods; this is the very mechanism by which Squirrel makes the instance’s properties and methods available to each other. Remember, methods and properties are simply functions and variables associated with a specific class and its instances.
Here then are the rules for this: if a class member function (ie. a method) is called using dot syntax, it is passed a reference to the class instance as its this variable. If a function is called in any other way, as we did when we called aFunction() in the example above, its calling context is passed into its this variable.
This is the default behavior. What if for some reason we need to change it — in other words, we want to make sure a reference to another object is passed into this? Since this is passed to the function implicitly not explicitly, we can’t simply include an object reference as an extra function parameter. This is where Squirrel’s bindenv(), call() and a number of related functions come into play. But before we look at what these functions do, let’s see why we might want to change the object passed automatically into a function.
The most common example is a class which defines functions that may be executed by impOS™ as a callback. Callbacks work by passing impOS, typically through a call such as imp.wakeup(), device.on() and server.onunexpecteddisconnect(), a reference to a function, either through its name or as an inline function definition, that the OS will run when, respectively, the timer fires, the specified message comes in from the agent, or the imp disconnects unexpectedly.
The passed reference tells the system which function to call, but gives no indication which would be the correct context object to pass onto the function’s this parameter. By default impOS passes a reference to the global table, which is the program’s outermost block.
But what if we need the function called to be able to access specific variables, such as an object’s instance variables? They will be outside of the scope of the main program context. Fortunately, we can ensure the function is passed not a reference to the calling context, whatever that happens to be, but a reference to a different object, thus overriding the default behavior outlined above.
Specifying the object that gets passed to a function is what call() and bindenv() do. They differ in how they are applied.
Squirrel’s call() method is a delegate method that applies only to functions. Its usage is straightforward: it replaces a standard function call in order to give you direct access to the value that will be passed to the function’s this parameter.
If a call to a regular function looks like this:
doSomething(42, "fish");
we’ve already seen that this is actually:
doSomething(this, 42, "fish");
but that this is hidden from view. However, call() provides an initial parameter that will be fed into this:
doSomething.call(myInstance, 42, "fish");
Let’s say we define a class and some code to make use of it:
class MyClass {
usefulArray = [ . . . ];
function setSomething(value) {
// . . .
}
}
local myInstance = MyClass();
function doSomething() {
local dataToUse = usefulArray.top();
}
doSomething();
Calling doSomething() will generate an error, because the variable usefulArray is not defined within the scope of the function. To deal with this, we can modify the doSomething() call using call():
doSomething.call(myInstance);
Here, instead of passing doSomething() the main program context as its this value, this is set to the object myInstance. This puts myInstance’s method setSomething() and its property usefulArray into doSomething()’s reach, making myInstance’s functionality and data directly accessible to doSomething()’s code. So call() provides a way to give one object temporary access to another object’s components when the first object is unable to access them directly, typically because the two are not in either’s scope.
If we call doSomething() again but without the call() delegate method, it will have its usual context object passed into this (which will once again generate an error).
This is a variation on call(). It takes a single parameter: an array containing all of the target function’s parameters, plus this, as elements. Where a function might, for instance, take three parameters:
doSomething(value1, value2, value3);
and be called with appropriate values:
doSomething(42, "fish", "biro");
the acall() method passes these parameters as a single array:
local array = [myInstance, 42, "fish", "biro"];
doSomething.acall(array);
The array passed to acall() must include sufficient elements to cover all of the function’s parameters, including the usually hidden this. Thus doSomething() has four parameters — this plus the exposed value1, value2 and value3 — and so the array passed to acall() must have four elements too.
Both call() and acall() provide a way to pass an alternative context object on a one-off basis at the moment the target function is called. What if we want to associate this with an alternative context object when the function is not called directly but passed as a reference for some other code, such as impOS, to call? This is where bindenv() comes into play.
This method’s name is short for ‘bind to environment’. ‘Environment’ is, as we’ve seen, Squirrel-speak for the object that calls a function, or a class instance. It’s the same thing as the context object. bindenv() ties the function’s this variable to a specific object, passed to bindenv()) as a parameter. That sounds a lot like call(), but unlike call(), bindenv() clones the function, sets the value of its this parameter and then returns a reference to this new entity.
Returning a reference to a function rather than calling a function directly makes bindenv() particularly useful when working with callbacks. If we include in a class definition imp API calls that require a callback function, it is a straightforward process if the callback doesn’t need to access data held by an instance of that class. If it does, we seem to be in trouble.
The following code shows why. It defines a class to manage a switch connected to an imp. It augments the basic ‘is it on or off?’ functionality provided by the imp API with code to sidestep the multiple signals sent as the metal in the switch rapidly makes and breaks contact under the pressure of a user’s finger. The user presses the button once, but because of the physical nature of the device, the switch appears to connect and disconnect rapidly over a short period of time before remaining connected. This behavior is called ‘bouncing’ and it’s an inherent aspect of most electrical switches. Dealing with the problem, typically by waiting a short period of time for the secondary signals to stop is called ‘debouncing’.
class Button {
_pin = null;
_pull = null;
_polarity = null;
_pressCallback = null;
_releaseCallback = null;
constructor(pin, pull, polarity, pressCallback, releaseCallback) {
_pin = pin;
_pull = pull;
_pin.configure(_pull, debounce);
_polarity = polarity;
_pressCallback = pressCallback; // Function to call on a button press
_releaseCallback = releaseCallback; // Function to call on a button release
}
function debounce() {
_pin.configure(_pull);
imp.wakeup(0.01, getState);
}
function getState() {
if (_polarity == _pin.read()) {
if (_releaseCallback != null) _releaseCallback();
} else {
if (_pressCallback != null) _pressCallback();
}
_pin.configure(_pull, debounce);
}
}
When we instantiate the class as an object, we tell the instance to configure the pin to which the switch is connected so it will call the method debounce() when the state of that pin changes. The plan is that when debounce() is called for the first time, we remove the callback (preventing the next few switch state changes from having any effect) and set the imp to wake in 10ms and call getState(). This second method checks the pin state after all the secondary oscillations have had enough time to die down and for the true state of the switch to be revealed. It then triggers the appropriate callback and re-enables the pin state-change callback.
That is how the code is supposed to work — in fact, it fails: Squirrel reports an error, the index '_pin' does not exist
, when debounce() is called. What is wrong and how can the error be resolved?
As we’ve seen, in order for a called method to ‘know’ about its fellow instance methods and variables, it needs access to them through its context object, this. When debounce() is called by impOS in response to a change of the pin state, it is passed a context variable, but not one whose scope includes the property _pin. And so the first line of debounce() fails.
The answer is to use bindenv() to ensure the correct context is provided: in this case, the instance of the class. Three lines — two of them identical — need to be changed in the code above to make it work. These lines are in the constructor and the last line of getState():
_pin.configure(_pull, debounce.bindenv(this));
and the second line of debounce():
imp.wakeup(0.01, getState.bindenv(this));
The class constructor function is called when a new object is instantiated from that class. Its own this is a reference to the instance itself. Because debounce() and getState() will be called as callbacks, we have to pass references to those functions and we need to ensure that when they are called, they are passed the Button instance as their context object. bindenv() makes this happen. In the first of the two lines above, it clones debounce() and sets the clone’s context object to this which, as we’ve just seen, is a reference to the instance of Button.
Because the clone’s this now points to the Button instance, Squirrel has the information it needs to determine the value of _pin used in the function and to then bind that same instance to the clone of getState() generated in the second line above.
Again, this ensures that when impOS calls the clone of getState() it passes a reference to the Button instance into the clone’s this parameter, allowing it to access the instance’s _polarity, _pin, _releaseCallback, _pressCallback and _pull properties, and its debounce() method. And, again, we relay that instance on when we once more use bindenv() to clone debounce() ready for the next pin state-change callback.
Why don’t the other callback functions, _pressCallback() and _releaseCallback(), set in the class’ constructor and called in getState(), require the use of bindenv()?
They might indeed need it, depending on where they are instantiated. In the example code, taken from the Electric Imp’s GitHub repo, when the Button class is instantiated it is passed two inline functions as its state-change callbacks:
b1 <- Button(hardware.pin1, DIGITAL_IN_PULLDOWN, 0,
function() { server.log("Button 1 Pressed"); },
function() { server.log("Button 1 released"); }
);
As you can see, neither function cares what the value of their context variable is because they don’t access any of its variables or functions. So we needn’t concern ourselves which context is being passed when they are called.
What if we want to provide functions that are part of another context? This time we would indeed use bindenv() to ensure the correct instance data is included with the closure. If the context is known, we can hard-code its name into bindenv() just as we might with call(). More likely we don’t know its name, so we would pass the proxy this to bindenv().
For example, if we declare a variable debug in the main program and we want the log messages only to be printed if the value of this variable is true
. In this case, we do need to indicate the correct context, so that the callback has access to debug.
local debug = true;
b1 <- Button(hardware.pin1,
DIGITAL_IN_PULLDOWN,
0,
function() {
if (debug) server.log("Button 1 Pressed");
}.bindenv(this),
function() {
if (debug) server.log("Button 1 released");
}.bindenv(this)
);
We can add a bindenv() call to an inline function declaration as easily as we would to a named reference to a function.
It is perfectly possible to combine bindenv() and call():
local ff = f.bindenv(e1);
ff.call(e2, 10, 20);
In this case the function gets called with e1 as its context object, not e2. The bindenv() is so powerful that it overrules the attempted context-object change in call().
Squirrel’s support for closures has other advantages. For example, it allows you to bind callbacks to specific objects.
In the following code, we have an imaginary class, Thing, which can be used to interact with an Internet messaging protocol server. We expect to connect to a number of these servers; their URLs are stored in an (unshown) array, urls.
function connectHandler(result) {
// 'this' points to the bound object, whatever it is
if (result != THING_ERROR) {
// Not an error result, so send a message
local message { "body" : "I am about to disconnect from " + this.url };
this.postMessage(message);
} else {
// Error reported, so notify the user
server.error("Error connecting to " + this.url + " (code: " + result + ")");
}
// And disconnect
this.disconnect();
}
// Array to hold Thing instances
things <- [];
// Instantiate two Thing objects
local thingOne = Thing();
local thingTwo = Thing();
things.append(thingOne);
things.append(thingTwo);
foreach (index, thing in things) {
thing.onconnect(connectHandler.bindenv(thing));
thing.connect(urls[index], options);
}
The key line is in the foreach
loop: we set connectHandler() (ie. the same function) as each Thing instance’s onconnect callback and bind the function to the enumerated instance. This action creates two closures; each closure’s this references the specified instance: respectively, thingOne and thingTwo.
Squirrel ensures the correct closure is called in response to the triggering of a given Thing instance's onconnect callback. If thingTwo connects first, connectHandler() is called with this set to thingTwo. As a result, if there is no error, thingTwo’s url property is accessed, and its postMessage() method is called, followed by its disconnect() method.
In short, Squirrel performs the work we would otherwise have to do to determine which instance was responsible for triggering connectHandler().
Here there are two instances, but in a real-world application, there might be many. We can use the same handler for every instance any be sure that, even though we simply refer to the generic variable this, it will be set to reference the correct instance.
You might apply this technique to use a single function as a callback for multiple objects, such as I²C buses or UARTs by the device, or multiple asynchronous HTTP calls by the agent.