Flow 如何解释泛型类型?

How does Flow interpret generic types?

我想了解 Flow 如何决定将哪种类型用于泛型类型,以及是否有一种方法可以控制在什么级别推断泛型类型(我的意思将在下面进一步解释)。

This question is inspired by How to type a generic function that returns subtypes. I think there is a distinction between the two questions because this one focuses on understanding how T is chosen, where as the linked on is focuses on typing the return type of a function.

恒等函数是一个很好的剖析示例。它的类型相当简单

function identity<T>(value: T): T;

这似乎是足够的信息来了解实施应该是什么。但是,我觉得这种类型不足以了解身份功能的实际作用。例如,我们可以(正如链接问题试图做的那样),

function identity<T>(value: T): T {
  if (typeof value === 'string') {
    return '';
  }

  return value;
}

Try Flow

这不会进行类型检查,Flow 会抱怨 return 输入空字符串。但是,我认为在许多语言中这会很好——当输入 string 时,我们 returning 一个 string,否则我们 returning 原来的value 类型 T--但出于某种原因 Flow 不喜欢这样。

this answer 我的困惑更加复杂,我们可以 return value.substr(0, 0) 而不是空字符串,Flow 将不再抱怨,并且无法 return 严格相等的值,

function identity<T>(value: T): T {
  if (value === '') {
    return '';
  }

  return value;
}

Try Flow

我认为造成这种差异的一个主要原因是,除了 "JavaScript type" 之外,字面量在 Flow 中还可以像类型一样工作。例如,

const x: 5 = 5; // literal type
const x: number = 5; // JavaScript type

都有效。然而,这意味着当我们有一个 T => T 类型的函数时,我们不知道 Flow 是在推断字面量还是 JavaScript 类型作为类型。

我想知道是否有某种方法可以知道 Flow 为函数中的泛型类型推断出什么,或者是否有一种方法可以将泛型类型的范围限定在 "literal" 级别或 "JavaScript"级。有了这种能力,我们可以键入将值强制转换为该类型默认值的函数(即,字符串将变为空字符串,数字将变为 0)。这里函数的类型实际上是 T => T,但希望 Flow 可以避免抱怨 returning 默认值。

如果不能直接回答问题,希望能在这里阐明发生了什么。

让我们先以你的第一个例子为例:

function identity<T>(value: T): T {
  if (typeof value === 'string') {
    return '';
  }

  return value;
}

函数签名是identity<T>(T): T。这基本上是在说:

  1. 我们正在创建一个新类型 T,它可以是任何类型 (<T>)。
  2. 我们的函数将接收一个 T 类型的参数。
  3. 我们的函数将 return 类型为 T 的值。

从现在开始,none 个限制将改变,T 的类型也不会改变。 identity 必须 return 完全 类型 T,而不是其类型的子集。让我们看看为什么。

identity<'some string'>('some string');

在这种情况下,T 的类型是文字类型,'some string'。在调用上述函数的情况下,我们会发现 typeof value === 'string' 并尝试 return ''string。然而,stringT 的超类型,即 'some string',因此我们违反了函数的约定。

在简单字符串的情况下,这一切似乎都是人为的,但实际上是必要的,并且在扩展到更复杂的类型时更加明显。

让我们看看奇怪的身份函数的正确实现:

function identity<T>(value: T): T | string {
  if (typeof value === 'string') {
    return '';
  }

  return value;
}

A return 类型的 T 只能通过与 T 完全匹配的东西来满足,在我们的签名的情况下只能是 value。然而,我们有一个特殊情况,其中 identity 可能 return 一个 string,所以我们的 return 类型应该是 T | string 的联合(或者,如果我们想要超级具体,T | '').

现在让我们继续第二个例子:

function identity<T>(value: T): T {
  if (value === '') {
    return '';
  }

  return value;
}

在这种情况下,flow 只是不支持 value === '' 作为细化机制。流程中的细化非常挑剔,我喜欢将其视为一些简单的正则表达式的列表,这些正则表达式比我的代码 运行 多。确实只有一种方法可以将类型细化为字符串,那就是使用 typeof value === 'string'。其他比较不会细化为字符串。肯定还有一些关于改进泛型的问题,但像这样的东西工作正常(改进确实如此,当然它仍然表现出以前与泛型相关的错误):

function identity<T>(value: T): T {
  if (typeof value === 'string' && (value: string) === '') {
    return '';
  }

  return value;
}

(Try)

至于 substr 示例,对我来说这绝对像是一个错误。似乎您可以对 String 上 return 是 string 的任何方法执行相同的操作,例如 concatslice.

I would like to know if there is some way of either knowing what Flow infers for generic types in a function

在函数体中,流程并没有真正推断泛型的类型。泛型有一个具体的定义(TT,本质上是一个未知类型,除非它有边界,在这种情况下它是一个与这些边界匹配的未知类型)。 Flow 可能会推断出调用函数的参数类型,但这应该与函数的编写方式无关。

or if there is a way to scope the generic type to be at the "literal" level or "JavaScript" level. With this ability, we could type function that coerces values to the default value for that type (i.e., strings would go to the empty string, numbers would go to 0). Here the type of the function would effectively be T => T, but hopefully Flow could be prevented from complaining about returning the default values.

这里的问题是这将不再是 T => T。正如我在上面所展示的,打破这样的实现是微不足道的。