正在尝试创建类型安全的curried函数。我在SO上看到的所有答案都表明函数重载,这是我不愿做的。
我偶然发现了type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
它确实给出了函数的所有参数类型,并且可以根据其索引选择参数类型。但是,如何将ArgumentTypes切片为仅具有正确数量的参数及其类型?
在下面的例子中,curried(1, 1);给出了错误“预期的3个参数,但得到了2”。
function curry<T extends Function>(fn: T) {
const fnArgs: readonly string[] = args(fn);
const cachedArgs: readonly any[] = [];
type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
type FnArguments = ArgumentTypes<typeof fn>;
function curryReducer(...args: FnArguments) {
cachedArgs.push(...args);
return cachedArgs.length >= fnArgs.length
? fn(...cachedArgs)
: curryReducer;
}
return curryReducer;
}
function args<T extends Function>(fn: T): readonly string[] {
const match = fn
.toString()
.replace(/[\r\n\s]+/g, ' ')
.match(/(?:function\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/);
return match
? match
.slice(1, 3)
.join('')
.split(/\s*,\s*/)
: [];
}
function adding(a: number, b: number, c: number) {
return a + b + c;
}
const curried = curry(adding);
const add2 = curried(1, 1);
```;发布于 2021-01-06 04:22:32
我不建议使用正则表达式来操作字符串格式的函数-相反,您可以使用以下格式(在javascript中)创建一个非常简单的curry函数:
function curry (fn) {
return (...args) => {
if (args.length >= fn.length) {
return fn(...args);
}
return (...more) => curry(fn)(...args, ...more);
}
}此函数接受函数fn,然后返回一个新函数,该函数接受一定数量的参数(...args)。
然后,我们可以检查args是否与函数所需的参数数量一样长(这只是function.length)。这是基本情况,如下所示:
const add = (a, b, c) => a + b + c;
add.length // = 3
curry(add)(1, 2, 3) // = 6在我们应用的参数少于所需参数的情况下,我们需要返回一个新函数。此函数将接受更多参数(...more),并将这些参数附加到原始...args,即:
(...more) => curry(fn)(...args, ...more);要使用typescript来完成这项工作,这就有点复杂了。我建议看一看this tutorial,以便更好地理解。
我已经调整了他们的CurryV5变体(因为占位符不在讨论范围之内),并更新为使用更现代的typescript功能,因为语法更简单。这应该是获得你想要的行为的最低要求:
// Drop N entries from array T
type Drop<N extends number, T extends any[], I extends any[] = []> =
Length<I> extends N
? T
: Drop<N, Tail<T>, Prepend<Head<T>, I>>;
// Add element E to array A (i.e Prepend<0, [1, 2]> = [0, 1, 2])
type Prepend<E, A extends any[]> = [E, ...A];
// Get the tail of the array, i.e Tail<[0, 1, 2]> = [1, 2]
type Tail<A extends any[]> = A extends [any] ? [] : A extends [any, ...infer T] ? T : never;
// Get the head of the array, i.e Head<[0, 1, 2]> = 0
type Head<A extends any[]> = A extends [infer H] ? H : A extends [infer H, ...any] ? H : never;
// Get the length of an array
type Length<T extends any[]> = T['length'];
// Use type X if X is assignable to Y, otherwise Y
type Cast<X, Y> = X extends Y ? X : Y;
// Curry a function
type Curry<P extends any[], R> =
<T extends any[]>(...args: Cast<T, Partial<P>>) =>
Drop<Length<T>, P> extends [any, ...any[]]
? Curry<Cast<Drop<Length<T>, P>, any[]>, R>
: R;
function curry<P extends any[], R>(fn: (...args: P) => R) {
return ((...args: any[]) => {
if (args.length >= fn.length) {
return (fn as Function)(...args) as R;
}
return ((...more: any[]) => (curry(fn) as Function)(...args, ...more));
}) as unknown as Curry<P, R>;
}
const add = curry((a: number, b: number, c: number) => a + b + c);
const add2 = add(1, 1);
add2(3); // 5
add2(3, 4); // error - expected 0-1 arguments but got 2
add2('foo'); // error - expected parameter of type numberhttps://stackoverflow.com/questions/65564650
复制相似问题