Flow如何解释通用类型? [英] How does Flow interpret generic types?

查看:43
本文介绍了Flow如何解释通用类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想学习Flow如何决定用于泛型的类型,以及是否有一种方法可以控制在哪种级别上推断出泛型(我的意思是进一步解释).

此问题的灵感来自如何键入通用函数返回子类型.我认为这两个问题之间是有区别的,因为这个问题着重于理解如何选择T,而链接所关注的是着重于键入函数的返回类型.

身份功能是一个很好的例子.它的类型相当简单

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

这似乎是足够的信息,可以知道实现应该是什么.但是,我觉得这种类型不足以知道身份功能的实际作用.例如,我们可以(链接的问题尝试这样做)

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

  return value;
}

此答案使我感到困惑,在这里我们可以返回value.substr(0, 0)而不是空字符串和Flow将不再抱怨,并且由于无法返回严格相等的值,

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

  return value;
}

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

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

都有效.但是,这意味着当我们拥有类型为T => T的函数时,我们不知道Flow是否将文字或JavaScript类型作为类型推断.

我想知道是否有某种方法可以了解Flow对函数中的泛型类型的推断,或者是否有一种方法可以将泛型类型的作用域限定为文字"级别或"JavaScript"级别.使用此功能,我们可以键入将值强制转换为该类型的默认值的函数(即,字符串将变为空字符串,数字将变为0).这里的函数类型实际上是T => T,但是希望可以防止Flow抱怨返回默认值.

解决方案

希望在这里阐明所发生的事情,如果不能直接回答问题的话.

让我们首先举一个例子:

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

  return value;
}

功能签名为identity<T>(T): T.基本上是这样说的:

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

从现在开始,这些限制都不会改变,并且T的类型也不会改变. identity必须返回T exact 类型,而不是其类型的子集.让我们看看为什么.

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

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

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

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

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

  return value;
}

返回类型T只能由与T完全匹配的东西满足,在我们签名的情况下,该值只能为value.但是,我们有一个特殊情况,其中identity可能返回string,因此我们的返回类型应该是T | string的并集(或者,如果要超级具体,请返回T | '').

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

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

  return value;
}

在这种情况下,流只是不支持value === ''作为优化机制.流的优化非常挑剔,我喜欢将其看作是在我的代码上运行的一些简单正则表达式的列表.实际上,只有将类型细化为字符串的唯一方法,那就是使用typeof value === 'string'.其他比较不会细化为字符串.精炼泛型肯定也​​有一定的把握,但类似的方法可以很好地工作(精炼确实可以,但是仍然表现出先前与泛型相关的错误):

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

  return value;
}

( T,但希望Flow可以是 避免抱怨返回默认值.

这里的问题是,它将不再是T => T.如上所示,破坏这样的实现是微不足道的.

I would like to learn how Flow decides what type to use for a generic type, and if there is a way to control at what level the generic type gets inferred (what I mean by this is explained further down).

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.

The identity function is a great example to dissect. Its type is fairly straightforward

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

This seems like enough information to know what the implementation should be. However, I feel like this type is insufficient to know what the identity function actually does. For example, we could have (as the linked question tries to do),

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

  return value;
}

Try Flow

This does not typecheck, with Flow complaining about returning the empty string. However, I would imagine in many languages that this would be fine--we are returning a string when a string was inputted, otherwise we are returning the original value of type T--but for some reason Flow does not like this.

My confusion is compounded by both this answer, where we can return value.substr(0, 0) instead of the empty string and Flow will no longer complain, and by the inability to return a strictly equal value,

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

  return value;
}

Try Flow

I think a major reason for this discrepancy is that literals can act like types in Flow, in addition to the "JavaScript type". For example,

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

are both valid. However, this means that when we have a function of type T => T, we do not know if Flow is inferring the literal or JavaScript type as the type.

I would like to know if there is some way of either knowing what Flow infers for generic types in a function 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.

解决方案

Hoping to shed a little light here on what's going on, if not answer the question directly.

Let's take your first example first of all:

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

  return value;
}

The function signature is identity<T>(T): T. This is basically saying:

  1. We are creating a new type T which could be anything (<T>).
  2. Our function is going to receive a single argument of type T.
  3. Our function is going to return a value of type T.

From this point forward, none of these restrictions are going to change, and the type of T is also not going to change. identity must return the exact type of T, not a subset of its type. Let's look at why.

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

In this case the type of T is the literal type, 'some string'. In the case of this invocation of the above function, we would find that typeof value === 'string' and attempt to return '', a string. string, however, is a supertype of T which is 'some string', so we have violated the contract of the function.

This all seems rather contrived in the case of simple strings, but it's actually necessary, and much more obvious when scaling up to more complex types.

Let's look at a proper implementation of our weird identity function:

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

  return value;
}

A return type of T can only be satisfied by something which exactly matches T, which in the case of our signature can only be value. However, we have a special case where identity may return a string, so our return type should be a union of T | string (or, if we wanted to be super specific, T | '').

Now let's move on to this second example:

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

  return value;
}

In this case, flow just doesn't support value === '' as a refinement mechanism. Refinement in flow is very picky, I like to think of it as a list of a few simple regular expressions that are run over my code. There's really only way to refine the type to a string, and that's by using typeof value === 'string'. Other comparisons won't refine to string. There's definitely also some wonkiness around refining generics, but something like this works fine (the refinement does, it still exhibits the previous generic-related error, of course):

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

  return value;
}

(Try)

As for the substr example, that definitely looks like a bug to me. It seems you can do the same with any method on String that returns a string, such as concat or slice.

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

Within the function body flow doesn't really infer the type of a generic. A generic has a concrete definition (T is T, essentially an unknown type, unless it has bounds, in which case it is an unknown type that matches those bounds). Flow may infer the types of parameters going into invocations of the function, but that should have no bearing on how the functions are written.

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.

The problem here is that this would no longer be T => T. As I've shown above, breaking such an implementation is trivial.

这篇关于Flow如何解释通用类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆