Reputation: 8587
Let's image I have a number and I want to perform some arithmetic operations example.
x.add(3).subtract(2).print() // result 1
or alternatively
0.add(3).subtract(2).print() // result 1 (I have no code for this)
Currently I am trying the following code. I would like to know if there is a better more succinct way to achieve the same result,
Thanks
var test = function () {
var i = 0;
var add = function (j) {
i += j;
return this;
};
var subtract = function (j) {
i -= j;
return this;
};
var print = function () {
console.log(i);
};
return {
add: add,
subtract: subtract,
print: print
};
};
var x = test();
x.add(3).subtract(2).print();
Upvotes: 3
Views: 5361
Reputation: 898
I know this is an older post, but I was recently looking for a way to chain functions to make them more readable and came up with a simple recursive function that lets you chain as many "then" operations as you want then call "eval" to get the result.
You can do things like this.
// Result = 18 (Start with 3 then square it, then double it)
const result = then(3).then(square).then(double).eval();
function double(x) {
return x + x;
}
function square(x) {
return x * x;
}
You could pass in predefined functions like above or arrow functions like below. Your example could be done like this.
const result = then(0).then(x => x + 3).then(x => x - 2).eval();
It's not exactly the syntax you're looking for, but it allows indefinite chaining of operations.
Here's the code for "then".
function then(arg) {
return {
"eval": () => arg,
"then": (op) => then(op(arg))
};
}
Upvotes: 0
Reputation: 1075587
There are almost two separate answers to this question:
A hyper-general answer.
A general answer applicable to objects in, er, general.
A specific answer applicable to numbers.
You can chain functions any time an earlier function returns an object (or something that can be implicitly coerced to an object) that has another function as a property (loosely, a "method"). So for instance, you can chain .toString().toUpperCase()
on a Date
:
var dt = new Date();
console.log(dt.toString().toUpperCase();
...because Date.prototype.toString
returns a string, and strings have a toUpperCase
method.
One common and useful pattern is to have an object whose methods return a reference either to that same object or to another object of the same "type." (Sometimes people say "shape" rather than "type," since JavaScript is largely untyped.) jQuery uses this to great effect, for instance. $()
returns a jQuery object; depending on what method you call on it, you'll either get back the same jQuery object (for instance, each
) or a new jQuery object representing the result of the operation (for instance, find
). This pattern is quite useful, both with mutable objects and especially with immutable ones.
To do that in your own object: To return the same object just return this
. To return a new object of the same "shape," you'd use whatever construction mechanism you use for your object to create the new object to return.
So for instance, if we add a function at the beginning of your sequence that creates a "streamed" number, we'd either make it mutable and return this
each time:
function nstream(val) {
return {
// Our add operation returns this same instance
add: function(n) {
val += n;
return this;
},
// Same with multiply
multiply: function(n) {
val *= n;
return this;
},
// To access the underlying value, we need an accessor
result: function() {
return val;
},
// This provides compatibility with built-in operations
// such as + and *
valueOf: function() {
return val;
}
};
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);
Or we might make nstream
s immutable, where each operation returns a new nstream
:
function nstream(val) {
return {
add: function(n) {
return nstream(val + n);
},
// Same with multiply
multiply: function(n) {
return nstream(val * n);
},
// To access the underlying value, we need an accessor
result: function() {
return val;
},
// This provides compatibility with built-in operations
// such as + and *
valueOf: function() {
return val;
}
};
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);
Paradoxically, immutable objects like that can either increase or decrease memory pressure in your code, depending on whether they help you avoid defensive copies. For instance, if an nstream
were a member of another object and you wanted to know it couldn't change, if it were mutable you'd have to make a defensive copy when handing it out to other code; if it's immutable, you don't have to, but you have to create copies when "modifying" it. But costs and benefits of immutable objects are a bit afield of the question. :-)
As you're trying to call a method on a number, you'd have to add that method to Number.prototype
. Then you'd have your methods (add
, multiply
, etc.) return the result of the operation, which being a number will have your other methods. Here's a quick example:
Object.defineProperties(Number.prototype, {
add: {
value: function(n) {
return this + n;
}
},
multiply: {
value: function(n) {
return this * n;
}
}
});
console.log(1..add(3).multiply(4));
A couple of notes on that:
Object.defineProperty
or Object.defineProperties
without enumerable: true
so the new properties aren't enumerable. (Though with numbers it's not a really big deal. Much more so with arrays. And we leave plain objects [e.g., Object.prototype
] alone entirely.)..
in 1..add(3)
might look odd, but it's one of the two ways you call methods on numeric literals. The first .
is the decimal point. The second .
is the property accessor operator. Another way is (1).add(3)
because there's no confusion where the numeric literal ends. Obviously, this doesn't come up with variables: var n = 1; console.log(n.add(3).multiply(4));
works just fine.1..add(3)
creates a new Number object, then calls add
on that object. Within add
when we do return this + n
, the +
coerces the object back to its primitive value. Of course, all of this is optimized where possible and where it matters by the JavaScript engine.Upvotes: 10
Reputation: 465
You can chain methods as long as the return of previous method has the following one. So, in this case you need to create a "class" that thas has all the needed method and then return an "instance" of that class in all of them.
var myNum = function(n) {
var num = n;
this.add = function(a) {
var b = n+a;
return new myNum(b) ;
}
}
Upvotes: 1
Reputation: 386858
You could add some prototypes to the Number
object and call the value with a dot for floating point and a dot for the method.
Changing proptotypes of standard objects are not advisable, because they could lead to misunderstanding of code.
Number.prototype.add = function (v) { return this + v; };
Number.prototype.multiply = function (v) { return this * v; };
console.log(1..add(2).multiply(4)); // 12
Upvotes: 0