I am trying to develop a game engine for personal use in JS. I want to have my engine be able to use elements of separate classes. One such problem I am trying to solve is chaining methods from one class (say a math class that chains functions) to my main function.
Here is an example of what I want it to look like:
let game = new Engine()
game.operator(5).sum(3).divide(2)
Here's what it might be in my code, although this is where I am not sure what to do.
class Engine {
constructor() {
//Set up engine here
/* This is where I am unsure how to link the following operator class to this one.
* Do I put it here in constructor or ...
*/
}
/* ... do I put it here? (Or not in this class at all?)
*
* This is what I tried doing as a function
*
* operator(input) {
* let op = new Operator(input);
* }
*/
}
class Operator {
/*The following class is different than the one I am using, but follows similar syntax:
* Basic usage: operator(5).sum(3, 4) => Should output 12
* How the class works is mostly irrelevant, just know it chains methods.
*/
constructor(input) {
this.input = input;
this.answer = this.input;
}
sum() {
let a = arguments[0]
for(var i = 1; i < arguments.length; i++) {
a += arguments[i];
}
this.answer += a;
return this;
}
divide() {
let a = arguments[0];
for(var i = 1; i < arguments.length; i++) {
a *= arguments[i];
}
this.answer /= a;
return this;
}
}
How can I get one class to be able to chain methods from different class?
A pattern for chaining is to have the instance keep a chain state, and to provide a 'value' method that returns the chain state. To chain between two classes, I guess I'd include a special value method that returns an instance of the other class. (To keep the reader oriented, name it something that indicates the type change)...
class ObjectA {
constructor(string) {
this.chainValue = string
this.string = string
}
transformA() {
this.chainValue = this.chainValue.toUpperCase()
return this
}
transformB() {
this.chainValue = this.chainValue + "bar"
return this
}
// the regular value method
get value() {
return this.chainValue
}
// like the value method, but named to explicitly return MyNumber
get numberValue() {
return new MyNumber(this.value.length)
}
}
class MyNumber {
constructor(int) {
this.chainValue = int
this.int = int
}
add(n) {
this.chainValue += n
return this
}
get value() {
return this.chainValue
}
}
let a = new ObjectA("foo")
console.log(
a
.transformB() // append "bar"
.transformA() // convert to upper case
.numberValue // return a number (arbitrarily, the length of the chain state)
.add(12) // add 12
.value // expect 18
)
You can use Proxy for that purpose.
class Engine {
operator() {
// code
console.log('call operator')
}
}
class Operator {
sum() {
// code
console.log('call sum')
}
divide() {
console.log('call divide')
}
}
class SuperOperator {
negate() {
console.log('call negate')
}
}
const operator = new Operator();
const superOperator = new SuperOperator();
const engine = new Engine();
const objectsToChainFrom = [
engine,
operator,
superOperator,
];
// create helper function for proxy
function wrapper(originalMethod, ctx) {
return function () {
originalMethod.apply(ctx, arguments);
// return proxy;
return this;
}
}
var proxy1 = new Proxy(objectsToChainFrom, {
get(target, methodToCall, receiver) {
const objectWithMethod = target.find(el => el[methodToCall]);
return wrapper(objectWithMethod[methodToCall], objectWithMethod)
}
});
proxy1
.sum()
.operator()
.divide()
.negate()
Related
Can I create a method usable by each property of an object?
class myClass {
constructor() {
this.a = 1;
this.b = 2;
this.c = 3;
}
raise(x) {
this += x; //I know this doesn't work. I want the raise function to use
//the value of its caller and increase it by x
};
}
What I want to achieve is being able to call the raise method on any object property via
obj1 = new myClass();
obj1.a.raise(1); //a = 2
obj1.b.raise(3); //b = 5
obj1.c.raise(100); //c = 103
So I want to be able to use this syntax: object.property.method()
I tried creating raise on the class constructor, the class prototype or the Object prototype. I also couldn't find any mention of this elsewhere.
It would be impossible for obj.a to return a number, while also being able to call obj.a.raise to modify it.
If you're open to a small tweak, you could retrieve the value via a method called something like .get, while the underlying value is stored and reassigned in the _a property:
class myClass {
constructor() {
this._a = 1;
this.a = { raise: num => this._a += num, get: () => this._a };
}
}
const obj = new myClass();
obj.a.raise(1); //a = 2
console.log(obj.a.get());
Using ES6 you can do it easily. You may not need complex solution!
Sample is given below.
2nd Solution, to use composition.
class PriceList {
constructor(price, amount) {
this._price = price;
this._amount = amount;
}
get price() {
return this._price;
}
set price(p) {
this._price += p; // validation could be checked here such as only allowing non numerical values
}
get amount() {
return this._amount;
}
set amount(p) {
this._amount += p; // validation could be checked here such as only allowing non numerical values
}
}
const priceList = new PriceList(1, 10);
console.log(priceList); // PriceList { _price: 1, _amount: 10 }
priceList.amount = 10;
priceList.price = 100;
console.log(priceList); // PriceList { _price: 101, _amount: 20 }
class Value {
constructor(v) {
this._val = v;
}
get val() {
return this._val;
}
set(v) {
this._val = v;
}
raise(m) {
this._val += m;
}
}
class PriceList2 {
constructor(price, amount) {
this.amount = new Value(amount);
this.price = new Value(price);
}
}
const priceList2 = new PriceList2(1, 10);
console.log(priceList2);
priceList2.amount.raise(10);
priceList2.price.raise(100);
console.log(priceList2.amount.val); // 20
console.log(priceList2.price.val); // 101
priceList2.amount.val = 1
priceList2.amount.raise(10);
console.log(priceList2.amount.val); //30
.as-console-row {color: blue!important}
For literally all answers (except this one), you need to run a function to get the value, here it's more simplified.
class myClass {
constructor() {
this.a = new Number(1);
this.b = new Number(2);
this.c = new Number(3);
for (let p in this) {
this[p].raise = x => {
let fn = this[p].raise;
this[p] = new Number(x + this[p]);
this[p].raise = fn;
}
}
}
}
let m = new myClass();
m.a.raise(15);
m.a.raise(15);
// A = 1 + 15 + 15 = 31
m.b.raise(10);
m.b.raise(20);
// B = 2 + 10 + 20 = 32
m.c.raise(17);
m.c.raise(13);
// C = 3 + 17 + 13 = 33
console.log(m.a + 1, m.b + 1, m.c + 1) // 32 33 34
// logging just m.a, m.b, m.c will get you an object with a raise function
// only in stack overflow (or maybe other fake consoles), but the browser
// console will definitely work and give you a Number object
Can we use getters and setters without defining a method for a member?
For example, transform this
class int {
set value(val) {
this._value = val | 0; // Truncate
}
get value() {
return this._value;
}
}
var x = new int();
x.value = 5 / 2;
console.log(x.value); // shows 2 instead of 2.5
to something like this:
class int {
set (val) {
this = val | 0; // Truncate
}
get () {
return this;
}
}
var x = new int();
x = 5 / 2;
console.log(x); // shows 2 instead of 2.5
There's no operation you can tap into for when the value of a variable (x in your case) is replaced with a new value. That's just not something JavaScript has. You can't do that even with a Proxy.
Your first definition of int is probably about as close as you're going to get.
People have tried various ways of getting primitive-like things like your int. None of them is really satisfactory. For instance, this is a not-uncommon attempt:
class Int {
constructor(value) {
Object.defineProperty(this, "value", {
value: value | 0,
enumerable: true
});
}
set(value) {
return new this.constructor[Symbol.species](value);
}
valueOf() {
return this.value;
}
toString() {
return this.value; // Even though it's not a string
}
static get [Symbol.species]() {
return this;
}
}
then:
let n = new Int(5);
console.log(`n = ${n}`); // n = 5
n = n.set(n / 2);
console.log(`n = ${n}`); // n = 2
but as soon as you do something that doesn't coerce to a primitive, like:
console.log(n);
you see the object-ness of it. You have to do:
console.log(+n);
which makes it a pretty big footgun, though the immutability helps with things like let m = n..
Example:
class Int {
constructor(value) {
Object.defineProperty(this, "value", {
value: value | 0,
enumerable: true
});
}
set(value) {
return new this.constructor[Symbol.species](value);
}
valueOf() {
return this.value;
}
toString() {
return this.value; // Even though it's not a string
}
static get [Symbol.species]() {
return this;
}
}
let n = new Int(5);
console.log(`n = ${n}`); // n = 5
n = n.set(n / 2);
console.log(`n = ${n}`); // n = 2
// But
console.log(n); // (object representation of it)
Can this be written without complexing things with prototypes?
Why? The current code does what I want, but it bothers me how trixy it is to follow and how error prone it is, also seems to be performance wasting since things are duplicated.
Aim? The more I spend using prototype and this I get the sense the code would be simpler and more to the point if this was not the case.
Especially if the this-functions in SystemBlueprint can be rewritten to take an instance as argument instead. And if object Function Log() and Out could just be plain objects somehow? How can Log or Out be extracted outside of SystemBuilder?
Full code in Jsbin
https://jsbin.com/pigosijaxo/edit?js,console (Updated)
// Local for each System object
var SystemData = {
name: '?',
id: 1,
actions: [],
destinations: []
}
// Variables shared among all Systems
const SystemShare = {
global: 1
}
// this-Functions shared among all Systems
function SystemBlueprint() {}
SystemBlueprint.prototype = {
run() {
var length = this.actions.length
for (var i = 0; i < length; i++) {
var result = this.actions[i](arguments, this)
if (result && this.destinations.length > 0) {
for (var n = 0; n < this.destinations.length; n++) {
this.destinations[n].call(null, result)
}
}
}
},
does(algorithm) {
this.actions.push(algorithm)
return this
},
random(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
function SystemBuilder(name) {
// copy shared methods
var system = Object.create(SystemBlueprint.prototype)
Object.assign(system, JSON.parse(JSON.stringify(SystemData))) //deep copy
system.name = name
system.id = SystemShare.global++
function Log() {}
Log.prototype.local = () => console.log('fields: ' + JSON.stringify(Object.keys(system))),
system.log = new Log()
function Out(){}
Out.prototype.into = (destination) => {
system.destinations.push(destination)
return system
}
system.out = new Out()
system.trigger = {}
function OnEvent(trigger){
if(trigger === undefined) return
trigger.call(null, system.run.bind(system))
return system
}
system.trigger.on = new OnEvent()
return system
}
var system = new SystemBuilder()
system.my = 'Testing'
system.log.local()
system.does( () => 'printing output...')
system.out.into(console.log)
system.run()
Partial Answer, implementation from comment suggestion by #Bellian, a bit on the way for sure, thanks!
Where? Inside function SystemBuilder(...):
Instead of
function Log() {}
Log.prototype.local = () => console.log('fields: ' + JSON.stringify(Object.keys(system))),
system.log = new Log()
Do this
function _local(system){
console.log('fields: ' + JSON.stringify(Object.keys(system)))
}
system.log = {local: _local.bind(this, system)}
I am working my way through Eloquent JavaScript, and I am having questions about problem #6.3 (second edition).
Here is the problem:
Design an interface that abstracts iteration over a collection of
values. An object that provides this interface represents a sequence,
and the interface must somehow make it possible for code that uses
such an object to iterate over the sequence, looking at the element
values it is made up of and having some way to find out when the end
of the sequence is reached.
When you have specified your interface, try to write a function
logFive that takes a sequence object and calls console.log on its
first five elements—or fewer, if the sequence has fewer than five
elements.
Then implement an object type ArraySeq that wraps an array and allows
iteration over the array using the interface you designed. Implement
another object type RangeSeq that iterates over a range of integers
(taking from and to arguments to its constructor) instead.
I wrote this solution:
function ArraySeq(collection) {
this.values = collection;
}
ArraySeq.prototype.iterate = function(start, end, action) {
var n = Math.min(this.values.length, end);
for (var i = start; i < n; i++) {
action(this.values[i]);
}
};
function RangeSeq(from, to) {
var array = [];
for (var i = from; i <= to; i++)
array.push(i);
ArraySeq.call(this, array);
}
RangeSeq.prototype = Object.create(ArraySeq.prototype);
function logFive(sequenceObject) {
sequenceObject.iterate(0, 5, console.log);
}
//This code to test how the solution works was provided by the author
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
The solutions by the author are different, here are both of them:
// I am going to use a system where a sequence object has two methods:
//
// * next(), which returns a boolean indicating whether there are more
// elements in the sequence, and moves it forward to the next
// element when there are.
//
// * current(), which returns the current element, and should only be
// called after next() has returned true at least once.
function logFive(sequence) {
for (var i = 0; i < 5; i++) {
if (!sequence.next())
break;
console.log(sequence.current());
}
}
function ArraySeq(array) {
this.pos = -1;
this.array = array;
}
ArraySeq.prototype.next = function() {
if (this.pos >= this.array.length-1)
return false;
this.pos++;
return true;
};
ArraySeq.prototype.current = function() {
return this.array[this.pos];
};
function RangeSeq(from, to) {
this.pos = from - 1;
this.to = to;
}
RangeSeq.prototype.next = function() {
if (this.pos >= this.to)
return false;
this.pos++;
return true;
};
RangeSeq.prototype.current = function() {
return this.pos;
};
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
// This alternative approach represents the empty sequence as null,
// and gives non-empty sequences two methods:
//
// * head() returns the element at the start of the sequence.
//
// * rest() returns the rest of the sequence, or null if there are no
// elemements left.
//
// Because a JavaScript constructor can not return null, we add a make
// function to constructors of this type of sequence, which constructs
// a sequence, or returns null if the resulting sequence would be
// empty.
function logFive2(sequence) {
for (var i = 0; i < 5 && sequence != null; i++) {
console.log(sequence.head());
sequence = sequence.rest();
}
}
function ArraySeq2(array, offset) {
this.array = array;
this.offset = offset;
}
ArraySeq2.prototype.rest = function() {
return ArraySeq2.make(this.array, this.offset + 1);
};
ArraySeq2.prototype.head = function() {
return this.array[this.offset];
};
ArraySeq2.make = function(array, offset) {
if (offset == null) offset = 0;
if (offset >= array.length)
return null;
else
return new ArraySeq2(array, offset);
};
function RangeSeq2(from, to) {
this.from = from;
this.to = to;
}
RangeSeq2.prototype.rest = function() {
return RangeSeq2.make(this.from + 1, this.to);
};
RangeSeq2.prototype.head = function() {
return this.from;
};
RangeSeq2.make = function(from, to) {
if (from > to)
return null;
else
return new RangeSeq2(from, to);
};
logFive2(ArraySeq2.make([1, 2]));
// → 1
// → 2
logFive2(RangeSeq2.make(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
My questions:
My solution produces exactly the same results as the author's solutions. I tested all three with various test cases. I did utilize the material of the chapter when writing my solution (the chapter talks about OOP techniques in JavaScript, inheritance and such). Given all this, is my solution wrong or not?
Why are his solutions written the way they are written? I mean, is there something wrong with my approach? Not abstract enough? Is it a bad thing that my method accepts arguments? His methods do not require arguments. Are there any drawbacks to my solution comparing to his? (I must confess, I barely understood the problem, so my solution probably reflects the level of my understanding of the problem.)
Thank you!
Your solution is not correct, because the whole point of the iterator protocol is to iterate lazily, so that RangeSeq(1, 10000000) doesn't fill up memory with all elements if we only need five.
The author's code is ok, but unnecessarily verbose. Iterator protocols are usually implemented like this:
an Iterable is an object that has the iterator() method
Iterable.iterator() returns an Iterator object
Iterator provides the method .next which returns a pair (done, value). Alternatively, .next can throw an exception when called on an exhausted Iterator.
Examples:
function RangeSeq(from, to) {
this.iterator = function() {
var i = from;
return {
next: function() { return [i >= to, i++] }
}
}
}
function ArraySeq(ary) {
this.iterator = function() {
var i = 0;
return {
next: function() { return [ i >= ary.length, ary[i++]] }
}
}
}
function logN(seq, n) {
var it = seq.iterator();
while (n--) {
let [done, value] = it.next();
if (done) break;
console.log(value)
}
}
logN(new RangeSeq(100, 100000000), 5);
console.log('--------------');
logN(new ArraySeq([11,22,33,44,55,66,77,88,99]), 5);
That said, modern JS engines have this kind of stuff built-in, so the Range example can be written much simpler as
function RangeSeq(from, to) {
this[Symbol.iterator] = function*() {
for (let i = from; i < to; i++)
yield i;
}
}
n = 0;
rs = new RangeSeq(100, 10000000);
for (let x of rs) {
if (n++ >= 5) break;
console.log(x)
}
I'm creating a JavaScript library. I've been trying to implement chaining.
0: What I first came up with:
function V(p) {
return {
add : function(addend) { return V(p + addend); },
sub : function(subtra) { return V(p - subtra); },
};
}
Using this method I can chain easily:
V(3).add(7).sub(5) // V(5)
Unfortunately the result is always a wrapped V() function, I am unable to extract the resulting value this way. So I thought about this problem a bit and came up with two semi-solutions.
1: Passing flag to last method
function V(p, flag) {
if(flag)
return p;
else
return {
add : function(addend, flag) { return V(p + addend, flag); },
sub : function(subtra, flag) { return V(p - subtra, flag); }
};
}
Using this method I can end the chain by passing a flag to the last method I use:
V(3).add(7).sub(5, true) // 5
While this works just fine, it requires some code repetition and makes chaining less readable and my code less elegant.
2: Using start() and end() methods
_chain = false;
function V(p) {
function Wrap(w) {
return (_chain) ? V(w) : w;
}
return {
add : function(addend) { return Wrap(p + addend); },
sub : function(subtra) { return Wrap(p - subtra); },
start : function() { _chain = true; },
end : function() { _chain = false; return p; }
};
}
Using this method you can do single operations with no more code:
V(3).add(7) // 10
But chaining requires two more methods, making things a lot less readable:
V(3).start().add(7).sub(5).end() // 5
So basically I'm just searching for the best way to implement chaining into my library. Ideally I'm looking for something where I can use any number of methods and don't need to terminate the chain in inelegant ways.
V(3).add(7).sub(5) // 5, perfect chaining
Why not introducing a private variable and work on that? I guess that is even more convenient. Plus it's probably a good idea to have a pure "getter" that finally returns the computed value. This could look like this:
function V(p) {
var value = p;
return {
add: function(addend) {
value += addend;
return this;
},
sub: function(subtra) {
value -= subtra;
return this;
},
get: function() {
return value;
}
};
}
console.log(V(3).add(7).sub(5).get()); // 5
You cannot return the Object in a getter function obviously. So you need some method where the chaining ends and returns a value.
In some cases it does need to have something similar to end, but in your simple arithmetic example, it does not.
function V(initial_val){
if(!(this instanceof V)){
return new V(initial_val);
}
var num = initial_val || 0;
this.set = function(val){
num = val;
return this;
}
this.add = function(val){
num += val;
return this;
}
this.sub = function(val){
num -= val;
return this;
}
this.valueOf = function(){
return num;
}
this.toString = function(){
return ""+num;
}
}
By adding valueOf and toString functions to the object, you can access its primitive value. That is, you can do something like:
var num = V(0).add(1).sub(2), another_num = 3 + num; // num = -1 and another_num = 2;
I would amend Haochi's excellent answer as follows :
Using the prototype will be more efficient if you have many V objects and
in the toString function I invoke the generic number toString with whatever
arguments you care to give it.
function V (n) {
if (!(this instanceof V)) {
return new V (n);
}
this.num = +n || 0;
return this;
}
V.prototype = {
set: function (val) {
this.num = val;
return this;
},
add: function (val) {
this.num += val;
return this;
},
sub: function (val) {
this.num -= val;
return this;
},
valueOf: function () {
return this.num;
},
toString: function () {
return this.num.toString.apply (this.num, arguments);
}
}