20

I have a bunch of useful functions that I have collected during my whole life.

function one(num){
    return num+1;
}

function two(num){
    return num+2;
}

I can call them with two(two(one(5)))

But I would prefer to use (5).one().two().two()

How can I achieve this without using prototype?

I tried to see how underscore chain works, but their code is too intense to understand it

1
  • 1
    just for the record, yes, those are the functions that I have been collecting... Commented Feb 18, 2012 at 4:16

5 Answers 5

26

The dot syntax is reserved for objects. So you can do something like

function MyNumber(n) {
    var internal = Number(n);
    this.one = function() {
        internal += 1;
        // here comes the magic that allows chaining:
        return this;
    }
    // this.two analogous
    this.valueOf = function() {
        return internal;
    }
}

new MyNumber(5).one().two().two().valueOf(); // 10

Or you're going to implement these methods on the prototype of the native Number object/function. That would allow (5).one()...

Sign up to request clarification or add additional context in comments.

7 Comments

Of course you should implement that MyNumber thing with prototypes, too, because it is much more efficient.
The valueOf() is necessary?, I mean, must I always end my chainings with some kind of function that returns the val?
@mithril333221 - to make chaining work, the return value from each method MUST be an object of the right type. If you want something other than that out of the object, you have to call a method to get that info.
Yes and no. Of course you will need a result function, else you still have an Object. If thats OK, you don't need one, but it's the only way to get the native value back. Yet, valueOf() is somewhat special, just as toString(): It is used for the automagic type conversion. For example, new MyNumber(5)+1 === 6 :-)
Is there any way of doing something like MyNumber.myStaticChainedMethod()? Notice I'm not calling MyNumber but rather trying to chain the function itself. Thoughts?
|
7

In order to avoid having to call toValue at the end of the chain as in @Bergi's solution, you can use a function with attached methods. JS will call toValue automatically when trying to convert to it a primitive type.

function MyNumber(n) {
    function x () { }
    x.one = function() { n++; return this; };
    x.valueOf = function() { return n; };
    return x;
}

Then,

MyNumber(5).one().one()
> 7

1 Comment

Is there any way of doing something like MyNumber.myStaticChainedMethod()? Notice I'm not calling MyNumber but rather trying to chain the function itself. Thoughts?
3

A nice and general alternative is creating a custom function composition function

var go = function(x, fs){
   for(var i=0; i < fs.length; i++){
       x = fs[i](x);
   }
   return x;
}

You can call it like this:

go(5, [one, two, two])

I am personaly not a big fan of method chaining since it restricts you to a predefined set of functions and there is kind of an impedance mismatch between values inside the "chaining object" and free values outside.

7 Comments

I want to know what restrictions are
I meant that if you want to do a "chaining method' aproach you need to know what all the chainable methods will be when you define the chainer (since you need to add a method to the chainer prototype for each function). On the other hand, the function composition version doesn't need to know the functions beforehand so you can pass anything you want to it.
Still, you can add functions to the prototype every time you need them; the problem is just that they have to fit into the chainer construct with return this and use this._value. The downside of composition is that a library would pollute the global namespace, and a namespace to pull them together makes composition syntax harder.
@Bergi: While it is true that dynamic monkey patching gives tons of lexibility, it is still a pain to mess with the chainer like that and it also does not play very well one-shot anonymous functions. (Anyway, its a matter of personal choice in the end. I personaly try to only create an OO interface if I think I really need the polymorphism and dynamic dispatching)
Yes, certainly the choice is all about the type of values you're dealing with. +1 for that
|
1

Another alternative is to use lodash flow function. For example:

var five = _.flow(one, two, two)
five(5)

I prefer assigning a new chain to a variable. It gives it a clear name and encourages re-use.

Btw, lodash also helps in passing additional arguments to the functions of the chain. For example:

var addFive = _.flow(
   _.partialRight(_.add, 1),
   _.partialRight(_.add, 2),
   _.partialRight(_.add, 2)
)

There are many other useful functions to help in functional chaining, e.g., partial, spread, flip, negate, etc.

Comments

0

Basically there is no function composition in JS. Even if there had been, it would be in the reverse order of what you mention in your question. ie two . two . one because Math declares composition operator like that. The order you want is called piping.

Having said that if you really want composition with dot operator, you may still do it by overloading the . operator via the Proxy object. It's a slightly convoluted topic and you may check this nice blogpost for some ideas.

However the simplest approach for your need would be by reducing an array of functions as;

var pipe = (fs,x,y) => fs.reduce((r,f) => f(r),{x,y}),
    fs   = [ ({x,y}) => ( x++
                        , y++
                        , {x,y}
                        )
           , ({x,y}) => ( x*=3
                        , y*=3
                        ,{x,y}
                        )
           , ({x,y}) => ( x--
                        , y--
                        , {x,y}
                        )
           ];

var {x,y} = pipe(fs,1,2);
console.log(x,y);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.