Understanding Squirrel's use of Static Methods

I’m trying to structure some of my code so that it follows similar lines to the APIs made available already (ie i2c.configure(), i2c.write etc). I’m not sure whether this is implemented as a class (that is never instantiated) or a table of functions and properties. I’m curious to know why Squirrel treats the following code they way it does:

`class Test1 {
function configure(startValue){ index <- startValue; }

function increment(){ index <- index+1; }

function update(newValue){ index <- newValue; }

}

Test2 <- {
function configure(startValue){ index <- startValue; }

function increment(){ index++; }

function update(newValue){ index = newValue; }

}

class Test3 {
function configure(startValue){ index <- startValue; }

function increment(){ index++; }

function update(newValue){ index = newValue; }

}

Test1.configure(22);
server.log("Test1 is "+Test1.index);
Test1.increment();
server.log("Test1 is "+Test1.index);
Test1.update(33);
server.log("Test1 is "+Test1.index);

Test2.configure(44);
server.log("Test2 is "+Test2.index);
Test2.increment();
server.log("Test2 is "+Test2.index);
Test2.update(55);
server.log("Test2 is "+Test2.index);

Test3.configure(66);
server.log("Test3 is "+Test3.index);
Test3.increment();
server.log("Test3 is "+Test3.index);
Test3.update(77);
server.log("Test3 is "+Test3.index);
`

I want the property index to be persistent and indeed it is for the Test1 and Test2. It fails for Test3 with “trying to set ‘class’”.

The output is as follows:

[Device] Test1 is 22 [Device] Test1 is 23 [Device] Test1 is 33 [Device] Test2 is 44 [Device] Test2 is 45 [Device] Test2 is 55 [Device] Test3 is 66 [Device] ERROR: trying to set 'class' [Device] ERROR: at increment:20

Why does the class Test1 work and Test3 fail?
In general, if I’m trying to encapsulate functionality into a handy object that I can reuse across projects and easily drop in or pull out, should I simply implement it as a table (as in Test2)?

I cannot quite figure it out. '1 and '3 are classes. 2 is a table.

I would have guessed neither if your class examples would work because you only created the class and did not construct one before invoking it.

Here is an example of a simple class with a constructor in it. It is followed by a line that creates one instance of the class and assigns it to _voltsforcal in the root table.

I suggest creating instances of the class before trying to call it. I like the Test2 example that you made. It seems like a nice way to organize code if you are only going to need one instance of the functions.

`
class firstorderfilter {

//Smooths data and is configurable
//by passing an integer value
//determining the time constant of the filter

Sum = null;
value = null;
N = null;

constructor(_seed, _N){

Sum = _seed * _N;
N = _N;
value = _seed;

}

function refresh(_seed){
Sum = _seed * N;
value = _seed;
}

function update(newvalue){

Sum = Sum + newvalue - value;
value = Sum/N;
return value;

}

}

_voltsforcal <- firstorderfilter (0 ,15);`

I’m trying to structure some of my code so that it follows similar lines to the APIs made available already (ie i2c.configure(), i2c.write etc). I’m not sure whether this is implemented as a class (that is never instantiated) or a table of functions and properties.

The built-in imp functionality is… well, actually it’s a special low-memory-usage thing that user Squirrel doesn’t get access to. But it acts as if it’s a table full of class instances and functions:

`class Pin {…}; // class for pins
class I2C {…};

hardware <- {
pin1 = Pin(1); // class is instantiated 6 (or 12) times
pin2 = Pin(2);

i2c12 = I2C(1,2);
i2c89 = I2C(8,9);

voltage = function() {…}
};
`

Why does the class Test1 work and Test3 fail?

Mostly because, the "index <- startValue" lines do not set a property of the class – they set a property of the global root table, just as if that line had appeared outside any class. (Test1, Test2 and Test3 all share the same copy of “index”!) So, because these functions don’t use any properties of the class, they can be called without a class instance (like classmethods in Python, or static methods in C++). Your Test3::update function, on the other hand, does attempt to access a class property – so it fails if run without an instance.

In general, if I’m trying to encapsulate functionality into a handy object that I can reuse across projects and easily drop in or pull out, should I simply implement it as a table (as in Test2)?

That’s the most efficient way, yes. But because functions in a table aren’t “member functions” of the table in any special way, you’d need to pass the table to each of them:

function Test2_configure(startValue) { return { index = startValue; } } function Test2_increment(t2) { t2.index++ } function Test2_update(t2, newValue) { t2.index = newValue } ... t = Test2_configure(55); Test2_increment(t); Test2_update(t, 66);

Peter

Mostly because, the
"index <- startValue"
lines do not set a property of the class – they set a property of the global root table, just as if that line had appeared outside any class. (Test1, Test2 and Test3 all share the same copy of “index”!)

Peter, thanks for your clarification. However, if I test Test1.index after setting/changing Test2.index and Test3.index, it is still the value that I would expect. How can it be sharing the same copy? See below, I’ve only added to lines to see if Test1.index is 33 (or 55 or 66).

`Test1.configure(22);
server.log("Test1 is "+Test1.index);
Test1.increment();
server.log("Test1 is "+Test1.index);
Test1.update(33);
server.log("Test1 is "+Test1.index);

Test2.configure(44);
server.log("Test2 is "+Test2.index);
Test2.increment();
server.log("Test2 is "+Test2.index);
Test2.update(55);
server.log("Test2 is "+Test2.index);

server.log("Test1 is still "+Test1.index);

Test3.configure(66);
server.log("Test3 is "+Test3.index);

server.log("Test1 still is "+Test1.index);

Test3.increment();
server.log("Test3 is "+Test3.index);
Test3.update(77);
server.log("Test3 is "+Test3.index);
`

Output is…

[Device] Test1 is 22 [Device] Test1 is 23 [Device] Test1 is 33 [Device] Test2 is 44 [Device] Test2 is 45 [Device] Test2 is 55 [Device] Test1 is still 33 [Device] Test3 is 66 [Device] Test1 still is 33

The index properties seem to be independent to me.

try this. it appears to work.

`
class MyTestClass {

index = null;

  constructor(_index){
    
   index = _index;
  }


function increment(){ index = index+1; }

function update(newValue){ index = newValue; }

}

Test2 ← {

index = 0,

function configure(startValue){ this.index = startValue; },

function increment(){ this.index++; },

function update(newValue){ this.index = newValue; }

}

Test4 ← {

index = 0,

function configure(startValue){ this.index = startValue; },

function increment(){ this.index++; },

function update(newValue){ this.index = newValue; }

}

Test1 ← MyTestClass(22);

server.log("Test1 is "+Test1.index);
Test1.increment();
server.log("Test1 is "+Test1.index);
Test1.update(33);
server.log("Test1 is "+Test1.index);

Test2.configure(44);
server.log("Test2 is "+Test2.index);
Test2.increment();
server.log("Test2 is "+Test2.index);
Test2.update(55);
server.log("Test2 is "+Test2.index);

Test3 ← MyTestClass(66);
server.log("Test3 is "+Test3.index);
Test3.increment();
server.log("Test3 is "+Test3.index);
Test3.update(77);
server.log("Test3 is "+Test3.index);

Test4.configure(98);

server.log("Test4 is "+Test4.index);

server.log(“this is the Test2 table”)
foreach (i, v in Test2){
server.log(i + " " + v);
}

server.log(“this is the Test4 table”)
foreach (i, v in Test4){
server.log(i + " " + v);
}

server.log("Test2 is "+Test2.index);

`

Creates output

[Device] Test1 is 22 [Device] Test1 is 23 [Device] Test1 is 33 [Device] Test2 is 44 [Device] Test2 is 45 [Device] Test2 is 55 [Device] Test3 is 66 [Device] Test3 is 67 [Device] Test3 is 77 [Device] Test4 is 98 [Device] this is the Test2 table [Device] index 55 [Device] configure (function : 0x2000f2d8) [Device] increment (function : 0x2000f304) [Device] update (function : 0x2000f330) [Device] this is the Test4 table [Device] index 98 [Device] configure (function : 0x2000f454) [Device] increment (function : 0x2000f480) [Device] update (function : 0x2000f4ac) [Device] Test2 is 55

I am interested in this topic. I had tried to create a table of functions and values to wrap my wifi configuration code into one table. I never got it to work but as I learn I may get back to that.

Yes mjkuwp94, my interest in this is for the same reason. I understand why Peter would think that all the index references are to the same property in the global root table. But this doesn’t bear out with testing. I don’t know whether that is an artefact of Squirrel or a problem in Electric Imp’s implementation. It would be nice to know what is actually happening under the hood. In the meantime, I will continue to encapsulate my code blocks into tables (since it clearly works), unless I find that it leads to performance problems.