简体   繁体   中英

create compose function in functional programming approach

I am trying to understand how compose works by recreating compose. As part of that I've created a simple calculator to take a value and based on that value return interest.

https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d#.rxqm3dqje

Essentially ultimate goal is to create a function that can do below. https://github.com/ngrx/example-app/blob/master/src/app/reducers/index.ts#L150

Nice to have: been able to pass multiple deposit values, and and may be calculate compound interest over time.

It would be good have some comments so I understand what is going on from Functional programming approach.

(()=>{
    // Generic compose function to handle anything
    const compose = (...fns) => (x) => {
        return fns.reduceRight((acc,fn)=>{
            return fn(acc);
        }, x)
    };

    const getInterest = (value) => {
        if (value < 1000) {
            return 1 / 100
        }

        if (value < 10000) {
            return 2 / 100
        }

        return 3 / 100;
    };

    const getDeposit = (value) => {
        return value;
    };

    const calculator = compose(getDeposit, getInterest)

    console.log(calculator(1000)) // Should return value of 1000 * interest rate. I currently get 0.03
})();

The issue is that you never multiply the two values: value and interest .

You should therefore pass another function into the composition, which will multiply the two previous results.

This means that this function will need to get 2 arguments, while the other two only take one. In general a function could need any number of arguments. So the composer should be able to pass enough arguments to each function. Furthermore, functions may also return more than one value -- in the form of an array. These values should be made available as arguments for the next function in the chain, while keeping any previous returned values available as well.

Another thing is that although you have implemented compose to execute the functions from right to left, the sequence of function you pass seem to suggest you expect them to execute from left to right, first getDeposit , and then getInterest , even though in your case it works both ways. Still, I would suggest to switch their positions.

So here is how you can make all that work:

 (()=>{ // Generic compose function to handle anything const compose = (...fns) => (...args) => { return fns.reduceRight((acc,fn)=>{ // Call the function with all values we have gathered so far let ret = fn.apply(null, acc); // If function returns a non-array, turn it into an array with one value if (!Array.isArray(ret)) ret = [ret]; // Queue the returned value(s) back into the accumulator, so they can // serve as arguments for the next function call acc.unshift(...ret); return acc; }, args)[0]; // only return the last inserted value }; const getInterest = (value) => { return value < 1000 ? 0.01 : value < 10000 ? 0.02 : 0.03; }; const multiply = (a, b) => a * b; const getDeposit = (value) => value; // Be aware the the rightmost function is executed first: const calculator = compose(multiply, getInterest, getDeposit); console.log(calculator(1000)) // Returns 20, which is 0.02 * 1000 })(); 

Alternative: pass along an object

The above implementation is not a pure compose implementation, since it passes not only the previous function result on to the next, but all previous functions results. This is not disturbing, and opens doors for more complex functions, but if you wanted to stick more to the original compose idea, you have a problem to solve:

As you want to have a function in the chain that only returns the rate, the next function in the chain will then only get the rate -- nothing else. With just that one piece of information it is of course not possible to calculate the result, which also needs the value as input.

You could "solve" this, by letting getInterest return an object, that not only has the rate in it, but also the value that was passed to it. You could also implement this with an array.

Here it is with an object implementation:

 (()=>{ // Straightforward implementation: const compose = (...fns) => (...args) => { return fns.reduceRight((acc,fn)=>{ return fn(acc); }, args); }; // Return an object with two properties: value & interest const getInterest = (value) => ({ value, interest: value < 1000 ? 0.01 : value < 10000 ? 0.02 : 0.03 }); // Expect an object as argument, with two properties: const getInterestAmount = ({value, interest}) => value * interest; const getDeposit = (value) => value; // Be aware the the rightmost function is executed first: const calculator = compose(getInterestAmount, getInterest, getDeposit); console.log(calculator(1000)) // Returns 20, which is 0.02 * 1000 })(); 

With this approach you can pass along objects that have many more properties, and so anything becomes possible.

I actually liked your simple compose function (it just only works for unary functions), and I think you can also make it work for now by making these changes:

  • rename getInterest to ...Rate , since it returns a multiplier for a value.
  • add a new getInterest function that takes a "rate getter" and a "value" in curried form: getRate => x => getRate(x) * x
  • swap the order of your calculator arguments in compose

    I think compose usually works from right to left ( f => g => x => f(g(x)) ), and pipe works from left to right ( f => g => x => g(f(x)) )

 (()=>{ // Generic compose function to handle anything const compose = (...fns) => (x) => { return fns.reduceRight((acc,fn)=>{ return fn(acc); }, x) }; const defaultRate = (value) => { if (value < 1000) { return 1 / 100 } if (value < 10000) { return 2 / 100 } return 3 / 100; }; const getInterest = getRate => x => getRate(x) * x; const getDeposit = x => 1000; const calculator = compose(getInterest(defaultRate), getDeposit); console.log(calculator()); })(); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM