typescript - 带有 ArgumentTypes 的 n 参数的 Typescript 类型安全 curry 函数
问题描述
试图创建一个类型安全的柯里化函数。我在 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);
```;
解决方案
我不建议使用正则表达式来操作字符串格式的函数 - 相反,您可以使用以下格式(在 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
在我们应用的参数少于所需参数的情况下,我们需要返回一个新函数。此函数将接受更多参数(...更多)并将这些附加到原始 ...args,即:
(...more) => curry(fn)(...args, ...more);
为了使这个与打字稿一起玩,这有点复杂。我建议看一下本教程以更好地理解。
我已经调整了他们的 CurryV5 变体(因为占位符超出了范围)并更新为使用更现代的打字稿功能,因为语法更简单。这应该是获得您所追求的行为的最低要求:
// 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 number
推荐阅读
- typescript - 打字稿与联合的交集导致不存在的属性
- pandas - 如何使用 pandas 数据框过滤数据
- python - 为什么我的 RSI 计算与 Yahoo Finance 大相径庭?
- lua - 如何在 Lua 中通过 ESP32 和 NodeMCU 使用蓝牙
- doxygen - Doxygen:组子项目
- c - cs50 pset5 拼写器 - 循环永远运行并且“字典中的单词”冻结在第一个值
- node.js - 自动化 NuxtJS 静态站点生成
- mlp - Fluentbit - 解析多种格式的日志(log+json)
- c# - 使用 C# 读取/写入调试端口 (0x80h)
- flutter - Flutter TextFormField 提示标签和光标位置?