If you change filter
to use xs.filter(x => f(x))
instead of xs.filter(f)
it will work -
const filter = curry((f, xs) => xs.filter(x => f(x)))
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
The reason for this is because Array.prototype.filter passes three (3) arguments to the "callback" function,
callback
- Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:
element
- The current element being processed in the array.
index
(Optional) - The index of the current element being processed in the array.
array
(Optional) - The array filter was called upon.
The f
you are using in filter
is match(/q/i)
, and so when it is called by Array.prototype.filter
, you are getting three (3) extra arguments instead of the expected one (1). In the context of curry
, that means a.length
will be four (4), and since 4 === fn.length
is false
(where fn.length
is 2
), the returned value is curry(fn, a)
, which is another function. Since all functions are considered truthy values in JavaScript, the filter
call returns all of the input strings.
// your original code:
xs.filter(f)
// is equivalent to:
xs.filter((elem, index, arr) => f(elem, index, arr))
By changing filter to use ...filter(x => f(x))
, we only allow one (1) argument to be passed to the callback, and so curry
will evaluate 2 === 2
, which is true
, and the return value is the result of evaluating match
, which returns the expected true
or false
.
// the updated code:
xs.filter(x => f(x))
// is equivalent to:
xs.filter((elem, index, arr) => f(elem))
An alternative, and probably better option, is to change the ===
to >=
in your "es6" curry
-
const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length >= fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
)
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
This allows you to "overflow" function parameters "normally", which JavaScript has no problem with -
const foo = (a, b, c) => // has only three (3) parameters
console.log(a + b + c)
foo(1,2,3,4,5) // called with five (5) args
// still works
// => 6
Lastly here's some other ways I've written curry
over the past. I've tested that each of them produce the correct output for your problem -
by auxiliary loop -
const curry = f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (f.length, [])
}
versatile curryN
, works with variadic functions -
const curryN = n => f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (n, [])
};
// curry derived from curryN
const curry = f => curryN (f.length) (f)
spreads for days -
const curry = (f, ...xs) => (...ys) =>
f.length > xs.length + ys.length
? curry (f, ...xs, ...ys)
: f (...xs, ...ys)
homage to the lambda calculus and Howard Curry's fixed-point Y-combinator -
const U =
f => f (f)
const Y =
U (h => f => f (x => U (h) (f) (x)))
const curryN =
Y (h => xs => n => f =>
n === 0
? f (...xs)
: x => h ([...xs, x]) (n - 1) (f)
) ([])
const curry = f =>
curryN (f.length) (f)
and my personal favourites -
// for binary (2-arity) functions
const curry2 = f => x => y => f (x, y)
// for ternary (3-arity) functions
const curry3 = f => x => y => z => f (x, y, z)
// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f (...xs, ...ys)
Lastly a fun twist on @Donat's answer that enables anonymous recursion -
const U =
f => f (f)
const curry = fn =>
U (r => (...args) =>
args.length < fn.length
? U (r) .bind (null, ...args)
: fn (...args)
)