Is it a good idea to overcharge a method? - javascript

I have 3 classes, all extend the previous one.
Entity -> Body -> Player
Each one has a die() method which do very different things.
Entity.die() will call the db
Body.die() will animate the body
Player.die() will call the UI and play special sound.
I don't want to manually call Entity.die() inside Body.die method, mainly because I have many classes and many common methods and I don't want to forget something.
I wrote this little piece of code which does exactly this, the Error stack is easy to understand and points to the correct lines.
function overLoadMethods (parent, children) {
const methods = {}
for (let [fname, fn] of Object.entries(parent)) {
if (typeof fn === 'function') {
if (children[fname]) {
methods[fname] = function () {
fn()
children[fname]()
}
Object.defineProperty(methods[fname], 'name', { value: fname })
} else {
methods[fname] = fn
}
}
}
return methods
}
function createEntity () {
return {
die: () => {
console.log(new Error().stack)
console.log('entity die')
}
}
}
const bodyMethods = {
die: () => {
console.log(new Error().stack)
console.log('body die')
}
}
function createBody () {
const entity = createEntity()
const overLoadedMethods = overLoadMethods(entity, bodyMethods)
return {
...entity,
...bodyMethods,
...overLoadedMethods
}
}
const playerMethods = {
die: () => {
console.log(new Error().stack)
console.log('player die')
}
}
function createPlayer () {
const body = createBody()
const overLoadedMethods = overLoadMethods(body, playerMethods)
return {
...body,
...playerMethods,
...overLoadedMethods
}
}
const player = createPlayer()
// will call Entity.die() then Body.die() then Player.die()
player.die()
Everything is working fine but I never saw this pattern before and I guess there is a good reason which I'm unaware of.
Could someone point the weakness of this pattern if there is one (pretty sure there is) ?

Common Lisp has something similar. When you define a method in a derived class you can decide whether this method should be executed:
:before (i.e. the base method will be called automatically after specialized one)
:after (i.e. the base method will be called automatically before the specialized one)
:around (i.e. only the specialized method will be called, but inside its body you can call the base method with call-next-method that is a special syntax that allows calling base method with either the parameters specified by the caller or the parameters that you want to pass instead).
For example C++ only has around available for general methods (but without the ability to call the base version with original parameters) and forces instead use of before in constructor and after in destructors.

I understand the desire to not repeat code and create code that makes it hard to make mistakes and forget things. But you still have code the you need to remember to wire up. For example, instead of calling Entity.die() you need to call overLoadMethods(). I'm not sure that's an improvement over regular of classes and calling super.die().
You can get the chained method behavior using ES6 classes (you can also get it using prototypes). This has a lot of advantages:
• The pattern is baked into the language.
• It's very clear to see parent/child relationship
• There's a lot of commentary, theory, and examples of different patterns
class Entity {
die() {
// Entity-specific behavior
console.log('entity die')
}
}
class Body extends Entity {
die() {
super.die()
// Body-specific behavior
console.log('body die')
}
}
class Player extends Body {
die() {
super.die()
// Player-specific behavior
console.log('player die')
}
}
const player = new Player
// will call Entity.die() then Body.die() then Player.die()
player.die()

Related

typescript resolving not actually optional optional property

I'm new to typescript to bear with me here if this is not how things are supposed to work.
I have a couple of goals in converting this js to ts.
Item = {}
Item.buy = function (id) {}
Item.sell = function (id) {}
I'm trying to get intellisense to autocomplete on Item. either buy or sell. I would also want to use dot notation to create these methods in arbitrary files without putting everything in the initial bracket. So I have something like this:
interface Item {}
const Item: Item = {};
interface Item {
buy?: Function
}
Item.buy = function () {
Item.render()
return "bought"
}
interface Item {
sell?: Function
}
Item.sell = function () {
Item.render()
return "sold"
}
interface Item {
render?: Function
}
Item.render = function () {
return 1
}
The problem here now is that render is an optional property and hence I get this error:
Cannot invoke an object which is possibly 'undefined'.
How can I make ts not check for this error? Since Item is not a class there's only ever going to be 1 item and it'll definitely have the render method, there is not ever going to be an instance where that error checking is useful. Or to put it another way, it's not actually optional, I only set it to be optional to work around const Item: Item = {}; erroring if I don't have it be optional.
Is there a way to let ts know that or use a different pattern in the first place?
SOLUTION 1:
Since you have not defined any method inside Item
interface Item {}
So you can check whether render method exist or not on Item as:
Item.buy = function () {
if(Item.render) Item.render(); // CHANGE
return "bought";
}
SOLUTION 2:
Best solution would be to add type of render on interface Item as:
interface Item {
render: () => void;
}
and then you can use it as:
Item.buy = function () {
Item.render();
return "bought";
}
My inclination here would be to use namespaces instead of an interface to hold these functions. It could look like this:
namespace Item {
export const buy = function () {
Item.render()
return "bought"
}
}
namespace Item {
export const sell = function () {
Item.render()
return "sold"
}
}
namespace Item {
export const render = function () {
return 1
}
}
Then you'd be able to access them the same way, as methods on the singleton Item value:
// elsewhere
console.log(Item.sell()); // "sold"
Note that namespace is a TypeScript specific feature, and nowadays new code is generally encouraged to use modules instead where possible. I don't really know if there's a good way to get this sort of behavior with modules, because the part we're using, merging different things into a common JS value, is not really how modules works. Maybe declaration merging and importing would give this to you, but I don't know.
Anyway, as long as you're okay with a TS-specific feature, then namespace would be an idiomatic way to represent this sort of gradual building of a singleton.
Playground link to code

Assign and Query Javascript Arrow Function for Metadata

The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.

Gremlin DSL usage errors within `repeat` step

We are using gremlin-javascript and have recently started to define a DSL to simplify our queries.
I am not sure if I've overlooked some caveat, but when attempting to use DSL methods within a repeat step, I consistently receive (...).someDslFunction is not a function errors, but using the same DSL function outside of repeat works without issue.
Here is a short (contrived) DSL definition that produces this issue:
class CustomDSLTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
hasNotLabel(...args) {
return this.not(__.hasLabel(...args));
}
filterNotLabel(...args) {
return this.filter(__.hasNotLabel(...args));
}
}
class CustomDSLTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, CustomDSLTraversalSource, CustomDSLTraversal);
}
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
...gremlin.process.statics
};
const __ = statics;
const g = traversal(CustomDSLTraversalSource).withRemote(connection);
And here are two uses of it, the first works without issue, the second causes the __.outE().(...).filterNotLabel is not a function error.
g.V('foo').outE().filterNotLabel('x', 'y').otherV(); // No errors
g.V('foo').repeat(__.outE().filterNotLabel('x', 'y').otherV()).times(1); // Error
// __.outE(...).filterNotLabel is not a function
EDIT: Thanks #stephen for pointing out the now so obvious issue:
I had redefined callOnEmptyTraversal for use with our DSL, and foolishly destructured the standard TinkerPop anonymous traversals into our custom ones. These obviously are calling the original callOnEmptyTraversal which does indeed use an instance of the base GraphTraversal.
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
mapToObject: (...args) => callOnEmptyTraversal('mapToObject', args),
...gremlin.process.statics // Whoops
};
const __ = statics;
SOLUTION: Just in case anyone else runs into this scenario. This is how I solved the issue of merging our DSL anonymous traversal spawns with the standard TinkerPop ones:
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
function mapToCallOnEmptyTraversal(s, fn) {
s[fn] = (...args) => callOnEmptyTraversal(fn, args);
return s;
}
const statics = ['hasNotLabel', 'mapToObject']
.concat(Object.keys(gremlin.process.statics))
.reduce(mapToCallOnEmptyTraversal, {});
const __ = statics;
I assume that the problem is that it's because you start your traversal with __ which is the standard TinkerPop spawn for anonymous traversals. As a result you get a GraphTraversal created rather than your CustomDSLTraversalSource. The TinkerPop gremlin-javascript documentation states that:
steps that are made available on a GraphTraversal should also be made available as spawns for anonymous traversals
So you probably should have your own version of __ that returns the CustomDSLTraversalSource. If you want to see more explicitly where things are going wrong, see in the code that callOnEmptyTraversal() returns GraphTraversal and obviously your DSL methods won't be available on that class.

How to reuse array functions in an infinite loop

The real code is larger, so I won't post it. It looks pretty much like this:
class A {
process(source) {
// I perform several operations with array helper functions here:
const filtered = source.filter(item => item);
const condition = filtered.some(item => item);
if (condition) {
const mapped = source.map(item => /* Mapping operations... */);
const sorted = mapped.sort((a, b) => { /* Some sort conditions... */ });
return sorted;
} else {
const mapped2 = filtered.map(item => /* A different mapping operation... */);
return mapped2;
}
}
}
const a = new A();
while (true) {
const source = getSourceFromSomewhere(); // Array (40 - 50 items aprox)
const b = a.process(source);
// ...
}
The problem: Basically, performance; "Don't make functions within a loop".
On every iteration a bunch of anonymous functions are getting created.
My solution:
class A {
// Predefine it:
sort() { /* Sort logic */ }
map() { /* Map logic */ }
map2() { /* Map logic */ }
filter() { /* Filter logic */ }
some() { /* Condition */ }
process(source) {
const filtered = source.filter(this.filter); // Note: Scope of 'this' is changed.
const condition = filtered.some(this.some);
if (condition) {
const mapped = source.map(this.map);
const sorted = mapped.sort(this.sort);
return sorted;
} else {
const mapped2 = filtered.map(this.map2);
return mapped2;
}
}
}
Another problem: Some of this functions need access to properties of the object itself, but the scope of this has been changed.
It's worth to call .bind(this) instead of creating the anonymous function? or pretty much the same?
What would you do in my case?
Thanks in advance.
To initialize bound functions within a class you could do
class Test {
fn = (t) => this[t]
}
basically the same what you wanted to do anyways.
The problem: Basically, performance; "Don't make functions within a loop".
Your premise is incorrect.
JavaScript engines are highly optimized. They do not laboriously read the source text character-by-character each time through a loop, or each time a function is called, much less each time a callback is invoked. They scan, parse, and pre-compile. At worst, functions like item => item will be created only once per function invocation. More likely, they will be pre-created during the initial scanning and parsing process.
Therefore, you don't need to worry about performance when considering whether to pre-define the functions yourself. The guiding principle should instead be program readability and structure.
If you do want to pre-define a function, as long as it does not use this, consider defining it outside the class:
function filterFunc(item) { return item.val < MAX; }
class A {
process() {
const filtered = source.filter(filterFunc);
If you do need 'this`, then in modern JS it is preferable to write
class A {
filterFunc(item) { return item.val < this.MAX; }
process() {
const filtered = source.filter(item => this.filterFunc(item));
instead of worrying about binding this.filterFunc making you write
class A {
constructor () { this.filterFunc = this.filterFunc.bind(this); }
process() {
const filtered = source.filter(this.filterFunc);
While as mentioned in another answer
class Test {
// constructor etc.
step = x => x + this.currentStep;
process() {
return this.arr.map(step);
}
}
would be a concise way to achieve your intended behavior, as this is already bound to the instance, it requires public class fields which is still in Stage 2, and therefore not yet supported in many browsers without a transpiler.
It is good to remember that you can always pass the this scope to the second argument of functions such as map and filter, so you don't have to manually bind your functions beforehand. The code then becomes
class Test {
// constructor etc.
step(x) { return x + this.currentStep; }
process() {
return this.arr.map(step, this);
}
}
This is very close to the solution you have in mind while making sure your functions have the correct scope.
Though I don't know much about inner workings of browsers I think if the code is hot enough (that is being ran often), the optimized compiler might not need to recreate those anonymous functions every run.

Javascript prototype function override when x

In my case, I'm using the Phaser framework.
So in this example I'm extending the Group class of phaser. Every 'actor' class (Sprite, Group, ...) calls upon the update() prototype every few miliseconds.
My idea was to extend this function only when the application runs on a desktop (so not on a phone).
for example:
var MousePointer = function (game, parent, name) {
Phaser.Group.call(this, game, parent, name);
this.init();
};
MousePointer.prototype = Object.create(Phaser.Group.prototype);
MousePointer.prototype.constructor = MousePointer;
MousePointer.prototype.init = function () {
// ... init
};
MousePointer.prototype.update = function () {
// Do something when on desktop
};
I can't possibly use an if clausule in the update() function to check whether the player is on dekstop/tablet/phone. So is there a way to actually override the prototype on initialisation?
for example (pseudocode):
if(onPhone)
MousePointer.prototype.update = parent.prototype.update;
else
MousePointer.prototype.update = this.update;
Well, you've kind of already written the answer for yourself, haven't you? This code (not inside the init method).
if(onPhone) {
MousePointer.prototype.update = function(){//Phone implementation};
} else {
MousePointer.prototype.update = function(){//Other implementation};
}
I advise against starting off with the "regular" function and then potentially overriding it, since you're just declaring it for nothing.
I think a better way to do this would be to write two different classes that shares the same parent, and then write different update() implementations for them. Then you can just do something like:
if(phone) {
var obj = new PhoneMousePointerObject();
} else {
var obj = new DesktopMousePointerObject();
}
// ... later
obj.update()

Categories

Resources