首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带有ArgumentTypes的带n参数的Typescript类型安全curry函数

带有ArgumentTypes的带n参数的Typescript类型安全curry函数
EN

Stack Overflow用户
提问于 2021-01-04 22:48:08
回答 1查看 154关注 0票数 0

正在尝试创建类型安全的curried函数。我在SO上看到的所有答案都表明函数重载,这是我不愿做的。

我偶然发现了type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;

它确实给出了函数的所有参数类型,并且可以根据其索引选择参数类型。但是,如何将ArgumentTypes切片为仅具有正确数量的参数及其类型?

在下面的例子中,curried(1, 1);给出了错误“预期的3个参数,但得到了2”。

代码语言:javascript
复制
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
复制
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-06 04:22:32

我不建议使用正则表达式来操作字符串格式的函数-相反,您可以使用以下格式(在javascript中)创建一个非常简单的curry函数:

代码语言:javascript
复制
function curry (fn) {
  return (...args) => {
    if (args.length >= fn.length) {
      return fn(...args);
    }

    return (...more) => curry(fn)(...args, ...more);
  }
}

此函数接受函数fn,然后返回一个新函数,该函数接受一定数量的参数(...args)。

然后,我们可以检查args是否与函数所需的参数数量一样长(这只是function.length)。这是基本情况,如下所示:

代码语言:javascript
复制
const add = (a, b, c) => a + b + c;
add.length // = 3

curry(add)(1, 2, 3) // = 6

在我们应用的参数少于所需参数的情况下,我们需要返回一个新函数。此函数将接受更多参数(...more),并将这些参数附加到原始...args,即:

代码语言:javascript
复制
 (...more) => curry(fn)(...args, ...more);

要使用typescript来完成这项工作,这就有点复杂了。我建议看一看this tutorial,以便更好地理解。

我已经调整了他们的CurryV5变体(因为占位符不在讨论范围之内),并更新为使用更现代的typescript功能,因为语法更简单。这应该是获得你想要的行为的最低要求:

代码语言:javascript
复制
// 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
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65564650

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档