Okay, thanks David. I will have to ponder your comments over a whiskey.
GaryDotzlaw:
Okay, thanks David. I will have to ponder your comments over a whiskey.
Another proven programming technique
David,
Perhaps we will both need a whiskey and discuss it offline. Its a very complicated subject, that’s for sure.
What we are trying to do, in Sebastian’s example, is to put the functions on the prototype of his Person constructor. We want to do this so that when we create a lot of person objects using the constructor, we are not consuming memory by having a copy of the functions in every object we create. So, in the example we have as follows:
// The constructor
function Person (firstName, lastName, title){
this.firstName = firstName;
this.lastName = lastName;
this.title = title;
}
Next we add the functions to the prototype like this:
// Adding to the prototype
var Person_proto = function(){
Person.prototype = {
fullName : function(){
return this.firstName + " " + this.lastName;
},
fullNameTitle : function(){
return this.fullName() + ", " + this.title;
}
}
}();
Then we create a new person object using the Person constructor like this:
var person = new Person('Some','Buddy','JavaScript Servoy Blogger');
application.output(person.fullNameTitle()); //Some Buddy, JavaScript Servoy Blogger
Now Servoy gives us a JSDoc warning at the application.output() statement; it does not see the method “fullNameTitle()” because it is on the prototype of the Person constructor (no code completion for it either). However, look what we see if we do a breakpoint at this line:
Notice that Person, the constructor, has the prototype with the two functions, and that person, the object we created, does not. This is what we want. When we create 10,000 person objects using the Person constructor, there is only one copy of the two functions in memory, and every person object has access to it. This is classical inheritance and what Sebastian wanted to do in his scenario.
On the other hand, the link (https://wiki.servoy.com/display/SERV61/Function) to the docs shows something different, I believe.
I can try to re-write the prototype in Sebastian’s example using the suggested approach there:
function Person_proto(firstName, lastName, title){
this.fullName = function(){
return this.firstName + " " + this.lastName;
};
this.fullNameTitle = function(){
return this.fullName() + ", " + this.title;
};
Person.apply(this, arguments);
}
Person_proto.prototype = new Person();
and then do this:
var oPerson2 = new Person_proto('SomeOther','Buddy','JavaScript Servoy Blogger');
application.output(oPerson2.fullNameTitle()); //SomeOther Buddy, JavaScript Servoy Blogger
It looks the same on the surface (I get the same output), but look at what I see if I set a breakpoint again:
Forgot to mention that this approach works great with code completion and no JSDoc warnings (just like the final approach shown at the end)
Now the functions are on the person object we created and not on the Person constructor. That means you have 10,000 copies of the functions in memory, definitely not what Sebastian wanted. The functions are not on the Person constructor, but rather on the Person_proto. All this approach is doing is prototype chaining; its copying the Person constructor and making a new constructor called Person_proto, augmenting it with two functions, and preserving the prototype chain back to Person. It looks identical to just doing this:
function Person (firstName, lastName, title){
this.firstName = firstName;
this.lastName = lastName;
this.title = title;
this.fullName = function(){
return this.firstName + " " + this.lastName;
};
this.fullNameTitle = function(){
return this.fullName() + ", " + this.title;
};
}
Anyway, I still don’t know how to get Servoy to see the prototype functions using the classical inheritance approach (first image). Clearly it is there in the Expressions tab, but only when I am running in the client and pause at a breakpoint. I think most editors look for the “prototype” keyword and the resolve it that way.
Again, I think we should probably take this offline. It could be we are misunderstanding each other and really need that drink first.
Hi Gary,
Great write-ups, tnx for sharing.
Servoy support the following:
/**
* @constructor
* @extends {personProto}
*/
function Person() {
}
function personProto() {
function privateMember(){}
this.name = function(){}
/**
* @protected
*/
this.protectedMember = function(){}
}
var initPerson = (function(){
Person.prototype = new personProto()
Person.prototype.constructor = Person //Just some extra code to properly set the constructor property, needed for some code constructions
}())
function test() {
var x = new Person()
x.name() //CodeCompleting for .name() and no builder markers
}
This will create one instance of the .name() function, shared between all instances of Person. The trick however is when the constructor takes arguments that should also be applied to the prototype. Then you need to do personProto.apply(this, arguments) as first thing inside the Person constructor function. The big downside of this is that all the logic defined inside the personProto function is applied against the Person instance, which means that new instances get created of all functions defined inside personProto, which sort of defies the whole purpose.
So, if you have want to have shared instances of all the function objects from the prototype AND you need to also apply constructor logic, you’re best off making it three layers: Person > personProtoLogic > personProtoFunctions (or something like that).
Note: the reason to do the “personProto.apply(this, arguments)” call first thing inside the Person constructor is because otherwise if inside Person you override methods already defined inside personProto, they will get overwritten by the “personProto.apply(this, arguments)” call.
As for encapsulation: on public members of constructor functions you can set the @protected JSDoc tag, which will make the member only visible inside subclasses and not to the outside world.
As you already noticed, we don’t support the Revealing Module pattern currently. Please file a feature request for that if you deem it important.
We also don’t support proper CodeCompletion/builder markers when using prototyping to alter objects, either through setting the prototype property with an Object or adding/overwriting members on the prototype object. Please vote for the cases related to this in our support system: SVY-5532
As for the suggestion to load big FoundSets into memory in one go: I think your reasoning is a bit flawed: FoundSets load PK’s and only get the rest of the record info when needed. So if you get a FS and then get the last record, a lot of PK’s will be loaded into memory and the full records at the end of the FS. If you would instead loop through the entire FS and get all the records, Servoy will start removing record info of records at the beginning of the FS when your loop progresses, to preserve memory usage. So, if you want to go for optimal performance caching-wise, you’re better of using a SQL/QueryBuilder statement to get the entire set of data as JSDataSet. Offcourse, if it’s a million records, you need to make sure there’s enough memory to deal with that.
Paul
GaryDotzlaw:
What we are trying to do, in Sebastian’s example, is to put the functions on the prototype of his Person constructor. We want to do this so that when we create a lot of person objects using the constructor, we are not consuming memory by having a copy of the functions in every object we create.
Ah crap, inheritance. You are absolutely correct, I completely missed your point. And now I see what you are trying to solve. So a moment to properly reflect on my embarrassment…
I started playing around with options and made it half-way through Paul’s approach but couldn’t figure it out (I’m not surprised now seeing his code). Then I started poking around with this (link1, link2) approach and I think it may be a winner:
/**
* @properties={typeid:35,uuid:"9E900714-C455-4156-BA3D-57285C38BB47",variableType:-4}
*/
var Line = {
// constructor
create: function (x1, y1, x2, y2) {
// type expression with instance and prototype items to get full code completion on instantiated objects
/**@type {{x1:Number,y1:Number,x2:Number,y2:Number, length:function(),seed:Number}} */
var line = Object.create(this);
// instance
line.x1 = x1;
line.y1 = y1;
line.x2 = x2;
line.y2 = y2;
return line;
},
// prototype
length: function () {
var dx = this.x2 - this.x1;
var dy = this.y2 - this.y1;
return Math.sqrt(dx * dx + dy * dy);
},
// prototype
seed: 50
};
/**
* @properties={typeid:24,uuid:"8B81F0CA-B902-4291-861B-AC7513FC54B2"}
*/
function test() {
// init objects
var obj1 = Line.create(0,0,0,100)
var obj2 = Line.create(0,100,0,100)
var obj3 = Line.create(100,0,0,0)
application.output("\nTest 1")
application.output("obj1: " + obj1.seed) // expect 50
application.output("obj2: " + obj2.seed) // expect 50
application.output("obj3: " + obj3.seed) // expect 50
// override prototype property "seed" for obj1
obj1.seed = 100
application.output("\nTest 2")
application.output("obj1: " + obj1.seed) // expect 100
application.output("obj2: " + obj2.seed) // expect 50
application.output("obj3: " + obj3.seed) // expect 50
// change prototype property "seed"
Line.seed = 0
application.output("\nTest 3")
application.output("obj1: " + obj1.seed) // expect 100
application.output("obj2: " + obj2.seed) // expect 0
application.output("obj3: " + obj3.seed) // expect 0
// create new prototype property (code completion doesn't work and get warning markers)
Line.added = "I was added to prototype"
application.output("\nTest 4")
application.output("obj1: " + obj1.added) // expect "I was added to prototype"
application.output("obj2: " + obj2.added) // expect "I was added to prototype"
application.output("obj3: " + obj3.added) // expect "I was added to prototype"
// create new prototype function (code completion doesn't work and get warning markers)
Line.addition = function () {
return this.x1 + this.x2 + this.y1 + this.y2
}
application.output("\nTest 5")
application.output("obj1: " + obj1.addition()) // expect 100
application.output("obj2: " + obj2.addition()) // expect 200
application.output("obj3: " + obj3.addition()) // expect 100
}
It doesn’t follow Servoy’s conventions for @constructor and @protected (don’t add protected items to the type expression declaration instead) and you create object instances without the “new” approach. And as expected code completion doesn’t work for:
We also don’t support proper CodeCompletion/builder markers when using prototyping to alter objects, either through setting the prototype property with an Object or adding/overwriting members on the prototype object. Please vote for the cases related to this in our support system: SVY-5532
Otherwise, I like it for its clarity and code completion on initial defined items. Be interested to see if it passes your object memory test. And Paul, thoughts?
ptalbot:
if you cache getSize() it will return 200 for a foundset that can have millions of records.
So your loop will stop at 200 and not iterate upon the whole foundset… quite a performance enhancement though!You should do
var iMax = databaseManager.getFoundsetCount($fs);
to get the real number of records if you want that cached…
This is true,
Gary,
If people use the sample code on the tutorial blindly and take the foundset.getSize() outside of the loop they will get strange results as Patrick mentioned.
Note that databaseManager.getFoundsetCount($fs) is also fairly expensive (it runs a select-count query).
Can you make this more explicit in the tutorial to prevent common mistakes?
Thanks,
Rob
On 4. Servoy lookups
The duplicate find as you mentioned should never be needed.
if (fs.find() || fs.find()) {
If the first find() returned false, the second should also.
If you can show this in a small solution then please file a case in our support system: https://support.servoy.com/
Rob
Paul, thank you for the feedback.
- I will digest the inheritance information and post my findings.
- I will also take a look at the loop performance using a dataset and see how it compares to the entire foundset.
Rob, thank you for the feedback.
- I agree databaseManager.getFoundsetCount() is an expensive operation. However, the performance test results show that using it to preload a large foundset before a loop, rather than stepping through them in 200 record chunks, is still the way to go.
- The sample code in my tutorial is correct; it preloads the entire foundset before the loop, and uses a cache. Patrick was referring to the discussion in this forum thread(I believe). I have modified the thread to make it clear that one needs to use the entire foundset in the loop.
- Regarding the find(), I would agree with you in theory. However, after running into this difficult to locate bug several times in different solutions, I am convinced it happens, have modified my Eclipse Javascript template to include it, and it will stay. I do not have a sample solution to demonstrate the issue, as I do not know why it happens when it happens. I originally ran into the problem several years ago and was shown the workaround by a Servoy engineer.
Thanks again everyone for your feedback.
Paul, the performance tip to use a dataset for the loop instead of the foundset was a good one; it is 2x as fast. That makes it 68x as fast as looping through records in 200 record chunks. Here is what the results look like (110,000 records):
Regarding the Object examples for code completion; Sorry, I am at a loss; it shouldn’t be this hard. I’m creating objects and the prototype using conventional methods (classical inheritance), and they work properly, except for code completion and build markers. Yes, you get code completion on the “methods” with your objects, but there are other problems.
Paul, I was unable to get your example to work at all. It throws a parse error at
Person.prototype.constructor = Person
I must be doing something wrong, because I had to modify your example to get it to run.
/**
* @constructor
* @extends {personProto}
*/
function Person(first, last, title) {
this.first = first;
this.last = last;
this.title = title;
}
function personProto() {
function privateMember(){}
this.fullName = function(){
return this.firstName + " " + this.lastName;
};
this.fullNameTitle = function(){
return this.fullName() + ", " + this.title;
};
/**
* @protected
*/
this.protectedMember = function(){
};
}
var initPerson = (function(){
Person.prototype = new personProto();
Person.prototype.constructor = Person;
})() // <-- I think I had to move the bracket here from your example as well
// and then later
var x = new Person('SomeOther','Buddy','JavaScript Servoy Blogger');
application.output(x.fullName());
// outputs undefined undefined
If I set a breakpoint and inspect the object, here is what I see:
The object looks correct, as the methods are on the prototype of Person, as I showed in my approach earlier in the thread. However, I could not get the prototype method to output the name of the person, just unidefined (I tried to follow your explanation but was unable to produce the required result) You will have to show us how to do that. I have to say, I suspect your approach is far more complicated then the classical inheritance approach I used earlier.
David, you do not get build markers, but your object does not look right when I inspect it. There is no prototype showing up at all, so although obj1.length outputs the right value, I have no clue where it is coming from. Your “seed” property for sure is on the object, and not the prototype. I ran the example you provided as is:
Paul, let me know if you can finish off my example using your approach. I would like to see it working.
Thanks guys,
Gary,
are you really doing these test, every time again, after a fresh restart?
Because when you do the first test, Servoy itself does also a bunch of caching.
Hi Gary,
The numbers on initial load are not the whole picture: The first two variations with both only keep a list of PK’s in memory (for most of the records in case you have many), so if you start using the FS to retrieve actual records, there will be the overhead of getting the record details from the database, while in the DataSet variant all data is already in memory. So in a real caching scenario, I expect the DataSet variant to outperform FoundSets even more than just the initial load difference.
Note that off course a DataSet is disconnected from the database, so any changes you (or another client) make are not reflected in the DataSet. You could resolve this by using the onDataBroadcast event to listen for changes in the underlying tables and update the DataSet accordingly. If done right, you can keep the cached DataSet also in memory between multiple calculation runs, so you need to get the DataSet only once per client session.
Another upside of the DataSet approach is that you can flatten your datastructure for easy processing (and again reducing back and forth’s to the database to retrieve related records) and get only those columns that you actually need.
Something else I forgot to mention: your memoization approach: you store the cache against “this”. But “this” for regular methods (so non-constructor functions called with the “new” keyword) points to the object they belong to. So in case of form or scope method, “this” points to the scope in which the method is declared. There is no problem dynamically adding a cache property to such a scope, but if you would have 2 memoization function in one scope, their caches would collide.
As for the example code I gave: if I copy and paste my example into a Script Editor in Servoy it works fine: no parse exceptions, no warnings and proper code completion. Which version of Servoy are you using?
The reason you cannot get it to output the name of the person is probably because you store it as this.first and you try to output this.firstName
As for not seeing the prototype in David’s approach: he uses Object.create, which creates a new object with the object passed into the create function as first parameter as prototype. However, when created this way, the prototype property is not exposed (don’t ask me why, it just how JavaScript is designed). You can get the prototype through Object.getPrototypeOf(yourObject). So Davids approach achieves the goal, but at the cost of having to type the object using JSDoc.
Paul
Hi Harjo,
Actually, the test results are after repeatedly running the procedures, since that better emulated the problem I was trying to solve in Optimizing Code Performance. The estimating engine in this case, was using the same foundsets over and over again.
If I do a restart between each test, I see numbers like this:
- 29666ms - loaded in chunks
- 7082ms - pre-load all before the loop
- 118ms - use a dataset
If I repeatedly run the procedures, in any order, without restart, I see numbers like this:
- 23914ms - loaded in chunks
- 238ms - pre-load all before the loop
- 118ms - use a dataset
So, to your point, pre-load seems to benefit more from the Servoy cache.
In my case, pre-loading the foundset before the loop, and avoiding foundset.getSize() in the for clause (cache the record count into a var and use that instead), significantly contributed to improving the estimating engine performance from 8.7secs to under 1sec in stress testing. It was a simple change to how I wrote the for loop with foundsets, and that was the entire point I was trying to communicate in the article; I did not have to do a massive code rewrite.
Paul,
-
Thanks for the further explanation on the dataset approach. I will keep this in mind for the future. In my original case, I did not want to rewrite a lot of code, so improving performance was easily achieved by simply writing a more efficient for loop using the foundset approach. No other code changes were needed in the methods, since they were designed originally to work with foundset records.
-
I agree, function memoization is for a single cache in the function, although you can store as much as you want in that single cache. I will update the article so that no one tries more than one cache in a function that end up colliding.
-
Thank you for catching my typo in the prototype methods. Your example is working now (final code below). Parse error is gone now as well (Servoy 7.3.0 build 2018).
-
Thank you for the explanation on why David’s example worked, despite not being able to see the prototype. I thought he was doing magic(another programming technique).
/**
* @constructor
* @extends {personProto}
*/
function Person(first, last, title) {
this.first = first;
this.last = last;
this.title = title;
}
function personProto() {
function privateMember(){}
this.fullName = function(){
return this.first + " " + this.last;
};
this.fullNameTitle = function(){
return this.fullName() + ", " + this.title;
};
/**
* @protected
*/
this.protectedMember = function(){
return "You cannot see me!";
};
}
var initPerson = (function(){
Person.prototype = new personProto();
Person.prototype.constructor = Person;
}())
// and then later to use
var x = new Person('SomeOther','Buddy','JavaScript Servoy Blogger');
application.output(x.fullName()) //CodeCompleting for .name() and no builder markers
// outputs SomeOther Buddy
Thanks again for the feedback.
I have added two more tutorials to my site. I modified the list at the start of this thread; refer to it for the complete library.
The two new tutorials are:
I hope you enjoy them!
Nice prototype inheritance and object based inheritance comparison: http://uxebu.com/blog/2011/02/23/object … ascript-5/
I have added a new tutorial to my site. I modified the list at the start of this thread; refer to it for the complete library.
The a new tutorial is:
I hope you enjoy it!
I have added a new tutorial to my site. I modified the list at the start of this thread; refer to it for the complete library.
The a new tutorial is:
I hope you enjoy it!
GaryDotzlaw:
I have added a new tutorial to my site. I modified the list at the start of this thread; refer to it for the complete library.The a new tutorial is:
I hope you enjoy it!
Thanks Gary, this tutorial is awesome!!
Thanks again Gary - don’t you ever sleep!
I use Arrays quite a bit - but beginning to realise how much more they can do. Really useful insights.
Cheers
Thanks guys.
I’ll sleep when I’m dead. - Bon Jovi
I have added a new tutorial to my site. I modified the list at the start of this thread; refer to it for the complete library.
The new tutorial is:
I hope you enjoy it!