【问题标题】:flowtype: how to annotate higher order function with parameter forwardingflowtype:如何使用参数转发注释高阶函数
【发布时间】:2018-09-10 12:03:33
【问题描述】:

我有一个函数foo

function foo(a: string, b: string, c: number, d: boolean): Promise<Result> {
  return new Promise(resolver => {
    ...
  });
}

// use foo
foo('hello', 'world', 3, true).then(...);

和一个高阶函数,它使用函数和参数使用柯里化:

function hof(func: Function) {
  return async function (...args: any[]) {
    // forward the args to the func and get the result
    const result = await func(...args);
    // do something else with the result
  }
}

// use foo with higher-order function `hof`
hof(foo)('hello', 'world', 3, true);
         ^^^^^^^^^^^^^^^^^^^^^^^^^ lack type annotations

如何使用flowtypehof函数的整个部分进行注解:

  • hof 接口
  • 转发参数...args:使用any[]会丢失原来的类型
  • 柯里化函数的返回类型

【问题讨论】:

  • 撞! 4年后有人解决这个问题吗?目前讨厌生活不得不使用 Flow 而不是 Typescript 来完成工作(仅供参考,Typescript 对此有一个简洁明了的解决方案)..

标签: flowtype


【解决方案1】:

TL;DR

我仍在寻找更好的选择,但到目前为止,这对我有用:

function foo(a: string, b: string, c: number): string {
  return 'Yay!'
}
// Alternatively, interface Arguments {
type Arguments = {
    <A>((A) => any): [A],
    <A, B>((A, B) => any): [A, B],    
    <A, B, C>((A, B, C) => any): [A, B, C],     
    <A, B, C, D>((A, B, C) => any): [A, B, C, D],
}

function hof <T: Function> (t: T): (...$Call<Arguments, T>) => void {
  return (...args) => undefined;
}

hof(foo);
hof(foo)("This", "works", 2);
// hof(foo)("this", "should't", "work");
// hof(foo)("Correctly", "fails", 2, "when there are extra args");

我遇到了类似的问题。也就是说,键入一个函数,该函数接受一个具有特定参数集的函数并返回一个接受相同参数集的新函数,我找不到明确的答案。

加长版

这是研究流类型问题并尝试错误的结果。 These are all the variations I tried that work in some degree

function foo(a: string, b: string, c: number): string {
  return 'Yay!'
}

// Attempt 1:
// Taken from 
// https://github.com/facebook/flow/issues/4672#issuecomment-377649789

declare function arguments<A>((A) => any): [A]
declare function arguments<A, B>((A, B) => any): [A, B]
declare function arguments<A, B, C>((A, B, C) => any): [A, B, C]
declare function arguments<A, B, C, D>((A, B, C, D) => any): [A, B, C, D]
type Arguments<T> = $Call<typeof arguments, T>

function hof1 <T: Function> (t: T): (...Arguments<T>) => void {
  return (...args) => undefined;
}

const augmentedFn = hof1(foo)("This", "works", 2);
// const augmentedFn2 = hof1(foo)("this", "should't", "work");
// const augmentedFn2 = hof1(foo)("Also", "check", 2, "number of args");

// Attempt 2: using array of mixed.
// This one doesn't seem to have limit to the arguments you can provide,
// but it doesn't seem to fail when extra args are passed.
// Notice that the return type is specified in the function signature

function hof2 <T: Array<mixed>>(func: (...T) => any): (...args: T) => any {
    return (...args) => null;
}

hof2(foo)("2", "2", 1);
// hof2(foo)(1, "this fails correctly", 1);
hof2(foo)("2", "2", 1, "This one has extra arg, but this method can't  limit them");

// If we define the generic return type in the returned function, flow breaks
function hof2broken <T: Array<mixed>>(func: (...T) => any) {
    return (...args: T) => null;
}
// hof2broken(foo)("This should work", "but fails with a cryptic message", 1);

// Attempt 3, using generics to capture the tuple values.
// It requires us to declare one generic for each arg in any function
// that uses them. Notice that I have to redeclare all the A, B, C and D generic
// types just to pass them to the tuple type.

type genericTuple<A, B, C, D> = [A] | [A, B] | [A, B, C] | [A, B, C, D]

function hof3<A, B, C, D, T: genericTuple<A, B, C, D>>(func: (...T) => any): (...args: T) => any {
  return (...args) => null;
}

hof3(foo)("This", "works", 2);
// hof3(foo)("this", "should't", "work");
hof3(foo)("Also", "fails", 2, "to check the argument's length");
// hof3(foo)("Also", "fails", 2, "to check the argument's length", "until it exceeds the number of possible args");

// Attempt 4
// It seems to be possible to ommit the generics by using tuples of 'any'
// This seems to have the same issues of not checking for extra unwanted args

type anyTuple = [any] | [any, any] | [any, any, any] | [any, any, any, any]

function hof4<T: anyTuple>(func: (...T) => any): (...args: T) => any {
  return (...args) => null;
}

hof4(foo)("This", "works", 2);
// hof4(foo)("this", "should't", "work");
hof4(foo)("Also", "fails", 2, "to check the argument's length");

// Attempt 5:
// Trying to extract the type calculated in 
// https://github.com/facebook/flow/issues/4672#issuecomment-377649789
// without getting redeclaration warnings
// This works in the 'try' console, but doesn't in the 0.80.0 version
// in Mac OS, so I can't stick with it
// I think that this working on the 'try' console is probably a bug

type ArgsFrom<T> = $Call<
  (<A>((A) => any) => [A]) &
  (<A, B>((A, B) => any) => [A, B]) &
  (<A, B, C>((A, B, C) => any) => [A, B, C]) &
  (<A, B, C, D>((A, B, C, D) => any) => [A, B, C, D]), T>

// type Arguments2<T> = $Call<argtest, T>  

function hof5 <T: Function> (t: T): (...ArgsFrom<T>) => void {
  return (...args) => undefined;
}

let testVar = hof5(foo);
hof5(foo)("This", "works", 2);
// hof5(foo)("this", "should't", "work");
// hof5(foo)("Correctly", "fails", 2, "when there are extra args");

// Attempt 6: I read somewhere that overloading is supported
// on interfaces, so I gave it a try. It seems to work and I will stick with it.

type Arguments3 = {
    <A>((A) => any): [A],
    <A, B>((A, B) => any): [A, B],    
    <A, B, C>((A, B, C) => any): [A, B, C],     
    <A, B, C, D>((A, B, C) => any): [A, B, C, D],
}

function hof6 <T: Function> (t: T): (...$Call<Arguments3, T>) => void {
  return (...args) => undefined;
}

hof6(foo);    
hof6((a: string, b: boolean) => null)("Also works", true); 
hof6(foo)("This", "works", 2);
hof6(foo)("this", "should't", "work");
hof6(foo)("Correctly", "fails", 2, "when there are extra args");

// Yay! Works and kind of makes sense 

【讨论】:

    猜你喜欢
    • 2020-01-14
    • 1970-01-01
    • 1970-01-01
    • 2018-06-27
    • 2019-09-22
    • 2017-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多