I'm trying to implement the curry() with placeholder support, that is curriable
. curriable
provides a high performance and small footprint curry
method. The version1 is the working code
is using map in the recursion function. However, I tried to use filter to filter the placeholder in the version2! Using filter is good for most cases, but fail with case curriedJoin(_,_,3,4)(1,_)(2,5)
withe reason Uncaught TypeError: curriedJoin(...)(...) is not a function
, can any one tell me why?
version 1
/**
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
*/
// function curry(fn) {
// // your code here
// }
function curry(func) {
return function curried(...args) {
const complete = args.length >= func.length && !args.slice(0, func.length).includes(curry.placeholder);
if(complete) {
args.length=3
args.sort((a,b)=>a-b)
return func.apply(this, args)
}
return function(...newArgs) {
// replace placeholders in args with values from newArgs using map
const res = args.map(arg => arg === curry.placeholder && newArgs.length ? newArgs.shift() : arg);
return curried(...res, ...newArgs);
}
}
}
const join = (a, b, c) => {
return `${a}_${b}_${c}`
}
curry.placeholder = Symbol()
const curriedJoin = curry(join)
const _ = curry.placeholder
console.log(curriedJoin(1, 2, 3)) // '1_2_3'
console.log(curriedJoin(_, 2)(1, 3)) // '1_2_3'
console.log(curriedJoin(_, _, _)(1)(_, 3)(2)) // '1_2_3'
console.log(curriedJoin(_,_,3,4)(1,_)(2,5))// '1_2_3'
Version2
/**
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
*/
// function curry(fn) {
// // your code here
// }
function curry(func) {
return function curried(...args) {
const complete = args.length >= func.length && !args.slice(0, func.length).includes(curry.placeholder);
if(complete) {
args.length=3
args.sort((a,b)=>a-b)
return func.apply(this, args)
}
return function(...newArgs) {
// replace placeholders in args with values from newArgs
const res = [...args].filter(element=> element!== curry.placeholder);
return curried(...res, ...newArgs);
}
}
}
const join = (a, b, c) => {
return `${a}_${b}_${c}`
}
curry.placeholder = Symbol()
const curriedJoin = curry(join)
const _ = curry.placeholder
console.log(curriedJoin(1, 2, 3)) // '1_2_3'
console.log(curriedJoin(_, 2)(1, 3)) // '1_2_3'
console.log(curriedJoin(_, _, _)(1)(_, 3)(2)) // '1_2_3'
console.log(curriedJoin(_,_,3,4)(1,_)(2,5)) //Fail, because "Uncaught TypeError: curriedJoin(...)(...) is not a function"
CodePudding user response:
If you add
console.log(curriedJoin(_,_,3,4)(1,_));
just before the failing line, you'll see that curriedJoin(_, _, 3, 4)(1, _) has already resolved to the string "1_3_4" ...which is not a function. Hence the error. The two versions behave quite differently (logging the various arg lists during processing is instructive.) The problem with using the "filter and append" approach is that it's not putting the args in the correct order. For the failing example, after one iteration, the 3rd placeholder ends up at the end of the arg list, beyond where the "complete" test is checking, instead of in the 2nd position where it belongs. What happens when filtering is that (_, _, 3, 4)(1, _) becomes (3,4,1,_), but the func only expects 3 good args, so this looks "complete" - oops! When using the "map" algorithm, (_, _, 3, 4)(1, _) becomes (1, _, 3, 4) which is clearly incomplete, so it goes into another recursion level as desired.
CodePudding user response:
Use of .sort
involves comparison of argument values and will not apply arguments with respect to their applied positions. Use of args.length = 3
will support only functions with 3 parameters.
Adding parameter n = f.length
supports currying of fixed-arity functions as well as variadic functions. Adding curry.merge
with sound inductive reasoning simplifies merging arguments and reduces complexity of curry
.
function curry(f, n = f.length) {
return function loop(...args) {
const a = args.slice(0, n)
return a.some(v => v === curry.placeholder)
? (...b) => loop(...curry.merge(a, b))
: f(...a)
}
}
curry.placeholder = Symbol("curry.placeholder")
curry.merge = (l, r) =>
l.length == 0 && r.length == 0
? []
: l.length == 0
? r
: r.length == 0
? l
: l[0] === curry.placeholder
? [r[0], ...curry.merge(l.slice(1), r.slice(1))]
: [l[0], ...curry.merge(l.slice(1), r)]
const _ = curry.placeholder
const add = curry((a,b,c) => a b c)
console.log(add(1, 2, 3)) // 6
console.log(add(_, 2)(1, 3)) // 6
console.log(add(_, _, _)(1)(_, 3)(2)) // 6
console.log(add(_,_,3,4)(1,_)(2,5)) // 6
const foo = curry(console.log, 3)
foo(1, 2, 3) // 1 2 3
foo(_, 2)(1, 3) // 1 2 3
foo(_, _, _)(1)(_, 3)(2) // 1 2 3
foo(_,_,3,4)(1,_)(2,5) // 1 2 3
Maybe a better name for curry
is partial
as the technique you are using here is more closely related to Partial Application.