函数表达式的流通用类型(箭头函数)
flow generic type for function expression (arrow functions)
我通常会尝试将流函数类型与其实现分开。当我写的时候它的可读性稍微好一点:
type Fn = string => string;
const aFn: Fn = name => `hello, ${ name }`;
而不是:
const aFn = (name: string): string => `hello, ${ name }`;
使用泛型类型时我们可以这样写:
const j= <T>(i: T): T => i;
const jString: string = j('apple'); // √
const jNumber: number = j(7); // √
但是如何将此类型与函数表达式分开?
type H<T> = (input: T) => T;
const h:H<*> = i => i; // --> WHAT SHOULD GO FOR '*'?
const hString: string = h('apple'); // X error
const hNumber: number = h(7); // X error
*
应该使用什么? any
可以,但这不是我想要的。
在 haskell 中,这不是问题:
identity :: a -> a
identity a = a
identity "a-string" // √
identity 666 // √
type H<T> = (input: T) => T;
const h2:H<*> = i => i;
const h3:H<*> = i => i;
const hString: string = h3('apple');
const hNumber: number = h2(7);
所以我注意到如果我使用 bounded generics,它会起作用:
type H<T> = <T: *>(input: T) => T;
const h:H<*> = i => i;
const a: string = h('apple'); // √
const b: number = h(7); // √
const c: {} = h({ nane: 'jon' }); // √
不要问我为什么。
我认为您的代码和类型定义的潜在问题是基于对特定 属性 泛型(又名参数多态性)的误解:Parametricity.
Parametricity 声明泛型函数必须不知道其多态 arguments/return 值的类型。它与类型无关。
因此,泛型函数必须平等对待与多态类型关联的每个值,而不管具体类型如何。当然,这是相当有限的。当一个函数对它的参数一无所知时,除了原封不动地返回它之外,它不能对它做任何事情。
让我们看一个不正确的泛型函数:
const f = <a>(x :a) :a => x + "bar"; // type error
f
没有按预期进行类型检查,因为 f
不能将 x
视为 String
。 Flow 强制参数化。
请注意,泛型在与高阶函数相关时更有用。这是正确的通用高阶函数的示例:
type F<X, Y> = (x: X) => Y;
const apply = <X, Y>(f: F<X, Y>, x: X) :Y => f(x);
const sqr = (x :number) :number => x * x;
const toUC = (s :string) :string => s.toUpperCase();
apply(sqr, 5); // does type check
apply(toUC, "foo"); // does type check
为什么要为每种可能的类型定义一个特定版本的 apply
?只要 f
和 x
的类型兼容,就可以将它应用于任意类型的值。
结论
当你有一个无限泛型函数时,你可以将它应用到你想要的任何值——它总是按预期工作。 所以没有理由为每种可能的类型声明不同的函数。聪明的人发明了多态性来避免这种情况。
但是问题仍然存在。一旦将泛型类型定义与函数声明分开,Flow 就不再强制执行参数化:
const f = <a>(x :a) :a => x + "bar"; // type error
type G<a> = a => a;
const g :G<*> = x => x + ""; // type checks!?!
所以你的问题仍然是合理的,不幸的是,我不能给你一个食谱。希望 Flow 的行为在未来的版本中有所改变。
在您自己的回答中,您建议使用有界泛型。我不会那样做,因为它解决了一个根本不存在的问题,因为它似乎是对这种多态性的滥用。
我通常会尝试将流函数类型与其实现分开。当我写的时候它的可读性稍微好一点:
type Fn = string => string;
const aFn: Fn = name => `hello, ${ name }`;
而不是:
const aFn = (name: string): string => `hello, ${ name }`;
使用泛型类型时我们可以这样写:
const j= <T>(i: T): T => i;
const jString: string = j('apple'); // √
const jNumber: number = j(7); // √
但是如何将此类型与函数表达式分开?
type H<T> = (input: T) => T;
const h:H<*> = i => i; // --> WHAT SHOULD GO FOR '*'?
const hString: string = h('apple'); // X error
const hNumber: number = h(7); // X error
*
应该使用什么? any
可以,但这不是我想要的。
在 haskell 中,这不是问题:
identity :: a -> a
identity a = a
identity "a-string" // √
identity 666 // √
type H<T> = (input: T) => T;
const h2:H<*> = i => i;
const h3:H<*> = i => i;
const hString: string = h3('apple');
const hNumber: number = h2(7);
所以我注意到如果我使用 bounded generics,它会起作用:
type H<T> = <T: *>(input: T) => T;
const h:H<*> = i => i;
const a: string = h('apple'); // √
const b: number = h(7); // √
const c: {} = h({ nane: 'jon' }); // √
不要问我为什么。
我认为您的代码和类型定义的潜在问题是基于对特定 属性 泛型(又名参数多态性)的误解:Parametricity.
Parametricity 声明泛型函数必须不知道其多态 arguments/return 值的类型。它与类型无关。
因此,泛型函数必须平等对待与多态类型关联的每个值,而不管具体类型如何。当然,这是相当有限的。当一个函数对它的参数一无所知时,除了原封不动地返回它之外,它不能对它做任何事情。
让我们看一个不正确的泛型函数:
const f = <a>(x :a) :a => x + "bar"; // type error
f
没有按预期进行类型检查,因为 f
不能将 x
视为 String
。 Flow 强制参数化。
请注意,泛型在与高阶函数相关时更有用。这是正确的通用高阶函数的示例:
type F<X, Y> = (x: X) => Y;
const apply = <X, Y>(f: F<X, Y>, x: X) :Y => f(x);
const sqr = (x :number) :number => x * x;
const toUC = (s :string) :string => s.toUpperCase();
apply(sqr, 5); // does type check
apply(toUC, "foo"); // does type check
为什么要为每种可能的类型定义一个特定版本的 apply
?只要 f
和 x
的类型兼容,就可以将它应用于任意类型的值。
结论
当你有一个无限泛型函数时,你可以将它应用到你想要的任何值——它总是按预期工作。 所以没有理由为每种可能的类型声明不同的函数。聪明的人发明了多态性来避免这种情况。
但是问题仍然存在。一旦将泛型类型定义与函数声明分开,Flow 就不再强制执行参数化:
const f = <a>(x :a) :a => x + "bar"; // type error
type G<a> = a => a;
const g :G<*> = x => x + ""; // type checks!?!
所以你的问题仍然是合理的,不幸的是,我不能给你一个食谱。希望 Flow 的行为在未来的版本中有所改变。
在您自己的回答中,您建议使用有界泛型。我不会那样做,因为它解决了一个根本不存在的问题,因为它似乎是对这种多态性的滥用。