getify / Functional-Light-JS

Pragmatic, balanced FP in JavaScript. @FLJSBook on twitter.
http://FLJSBook.com
Other
16.6k stars 1.96k forks source link

Caveat on Curried Props Example #194

Closed jacklaurencegaray closed 4 years ago

jacklaurencegaray commented 4 years ago

Chapter 3:

function curryProps(fn,arity = 1) {
    return (function nextCurried(prevArgsObj){
        return function curried(nextArgObj = {}){
            var [key] = Object.keys( nextArgObj );
            var allArgsObj = Object.assign(
                {}, prevArgsObj, { [key]: nextArgObj[key] }
            );

            if (Object.keys( allArgsObj ).length >= arity) {
                return fn( allArgsObj );
            }
            else {
                return nextCurried( allArgsObj );
            }
        };
    })( {} );
}

I guess it would be helpful to put a caveat to this that Object.keys( allArgsObj ).length >= arity checks only for number of keys dependent on the arity provided in the function call which makes both curriedPropsFn({ a: 1, b: 2, c: 3}) and curriedPropsFn({ x: 1, y: 2, z: 3}) valid calls.

getify commented 4 years ago

That's true, but isn't it similar to:

function f(x,y,z) { return x + y + z; }

f(1,2,3);  // 6 -- valid call
f.call([]);  // NaN -- but still valid call

IOW, you didn't fully provide what the function was expecting, but you still satisfied the conditions that it takes perform a function call with what was provided.

And to put it in traditional currying terms:

var f = x => y => z => x + y + z;

f(1)(2)(3);  // 6 -- valid call
f()()();  // NaN -- but still valid call
getify commented 4 years ago

For reference, the implementation here is basically almost identical to the one in my FPO lib:

https://github.com/getify/FPO/blob/09293e4190b92ac1814ccd5049cdc9a5d6283623/src/fpo.src.js#L210-L228

One slight difference I can spot there is that FPO filters out empty objects with the (keys.length > 0), which seems to be mostly a performance tweak (TBH can't recall).

In any case, there's no logic that says "you have to pass exactly the 'expected' parameters as named", just "you have to pass the expected number of parameters". AFAICT, historically the concept of "arity" has always been about count, not about name-matching of parameters.

Extending "arity" to mean name-matching is possible, but it seems a bit...surprising, perhaps? Typically, if you pass an object and it doesn't have a property named the way you expect, you just end up detecting that by accessing a property and getting back undefined... I figured it would be surprising for the function to have never been called at all in those cases, as your ability to "detect" that error condition would be that you still are holding a curried-function instead of the final computed result, instead of having the function internally be able to detect the error condition of "ahh, you didn't pass anything for x, that's a problem".

Does that make sense?