【问题标题】:Typescript type safe curry function with n-argument with ArgumentTypes带有 ArgumentTypes 的 n 参数的 Typescript 类型安全 curry 函数
【发布时间】:2021-01-06 04:22:32
【问题描述】:

试图创建一个类型安全的柯里化函数。我在 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);
```;

【问题讨论】:

    标签: typescript


    【解决方案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);
    

    要使用 typescript 来玩这个游戏,这有点复杂。我建议看看this tutorial 以更好地理解。

    我已经调整了他们的 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
    
    

    【讨论】:

      猜你喜欢
      • 2019-05-26
      • 2019-02-03
      • 1970-01-01
      • 2019-07-17
      • 1970-01-01
      • 2019-01-22
      • 2018-10-25
      • 2020-10-31
      • 2020-07-10
      相关资源
      最近更新 更多