将元组类型的值映射到不同的元组类型的值而无需强制转换 [英] Mapping tuple-typed value to different tuple-typed value without casts

查看:19
本文介绍了将元组类型的值映射到不同的元组类型的值而无需强制转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个元组类型的值,我想将它映射到不同类型的不同元组.元组可以是异质的.有没有办法在不使用下面示例中的类型转换的情况下执行此映射?

interface SomeType;{值:T}type SomeTypeList= { [K in keyof T]: SomeType}接口 OtherType{其他值:T}type OtherTypeList= { [K in keyof T]: OtherType}函数 convertValues(arr: SomeTypeList): OtherTypeList{//有没有办法在没有演员的情况下写这个?return arr.map(({ value }) => ({ otherValue: value })) as OtherTypeList}convertValues<[number, string, boolean]>([{ value: 1 }, { value: 'cat' }, { value: true }])//=>[{ otherValue: 1 }, { otherValue: 'cat' }, { otherValue: true }]

使用强制转换,它失去了类型安全性,因为回调可能会返回 ({ otherValue: 1 }) 并且一切都会正确进行类型检查.

解决方案

不,从 TS4.1 开始,这在 TypeScript 中是不可能的.我看到有两个问题;可以通过更改 Array.prototype.map(),但另一个需要对类型系统进行相当大的更改才能在一般情况下正常工作,并且目前无法以某种方式处理't,在某种意义上,相当于 类型断言(你称之为演员").


当前库类型 对于数组的 map() 方法是:

接口数组{map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];}

返回类型,U[] 是一个无序数组类型,对应于 callbackfn 参数的输出类型.它不是 元组.有一个开放的 GitHub 问题,microsoft/TypeScript#29841 要求对此进行更改,以便当你对一个元组调用 map() 时,你会得到一个相同长度的元组.目前尚未实施;但您可以使用 声明合并 来测试这样的自己换个衣服:

接口数组{map, U>(this: This, fn: (v: T) => U): { [K in keyof This]: U }}

如果我调用 convertValues()(将 arr 参数更改为 可变元组类型,它给编译器一个提示,它应该解释arr 如果可能的话作为元组),您可以看到它现在如何知道返回值中有多少元素:

function convertValues(arr: [...SomeTypeList]) {return arr.map(({ value }) => ({ otherValue: value }))}const ret = convertValues([{ value: 1 }, { value: 'cat' }, { value: true }])ret[0];ret[1];ret[2];//好的ret[3]//错误!长度为3"的元组类型在索引3"处没有元素.

不幸的是,每个元组元素的类型是未知的:

ret[0].otherValue.toFixed(2);//错误!//------------->~~~~~~~//类型 'string | 上不存在属性 'toFixed'数量 |布尔'.

每个元素只被编译器知道是 {otherValue: string |数量 |布尔值}.这并没有错,但也不是您要查找的内容.


所以让我们退后一步,考虑一下您需要 map() 怎样才能做到您想要的.当您调用 arr.map() 时,您将回调视为 泛型 函数,将泛型类型 SomeType 的输入转换为 OtherType 类型的输出,对于任何 <代码>V.否则就没有机会让编译器注意到输入元组的每个元素和输出元组的对应元素之间的相关性.您确实可以在调用 map() 时通过注释回调来编写:

function convertValues(arr: [...SomeTypeList]) {返回 arr.map(<V,>({ value }: SomeType):其他类型=>({ otherValue: value }))}

问题是……你如何描述这种可能做其他事情的通用回调?我认为您不想对 map() 的类型进行硬编码,以关心专门将 SomeType 转换为 OtherType 的回调代码>.您想说将 F 转换为 G 的回调,用于任何 FG>":

//无效的 TypeScript,不要使用它接口阵列 T{map},fn:<V>(v:F<V>)=>G V): { [A 中的 K]: G<A[K]>}}

但是没有办法在 TypeScript 中表达这一点.上面的FG不是通用的类型,而是通用的类型函数类型构造函数>.而这些在 TypeScript 中是不存在的.泛型类型构造函数需要引入所谓的更高级的类型在一些更注重函数式编程的语言中,比如 Haskell 和 Scala.有一个长期开放的功能请求,microsoft/TypeScript#1213 要求这样做,但是谁知道它是否会被实施.这将是相当多的工作,所以我不会屏住呼吸(但很想看到它!).并且有一些可能的方法可以在 TypeScript 中模拟更高级的类型(您可以阅读 GitHub 问题了解更多信息),但我不想为您的用例推荐任何东西.

所以我们被困住了.目前无法编写 map() 的类型来让编译器验证 convertValues() 的实现是否符合您声称要返回的类型.>


并且当编译器无法验证某事物的类型是否与您声称的一样,并且您确信您的声明仍然正确时,您几乎需要执行诸如类型断言之类的操作,因为您'已经做了.因此,我建议您继续按照此处显示的方式进行操作,并重新审视 TypeScript 中是否出现了更高级的类型.

代码的游乐场链接

I have one value of some tuple type, and I want to map it to a different tuple of a different type. The tuples are allowed to be heterogenous. Is there a way of performing this mapping without using the type cast in the example below?

interface SomeType<T> {
  value: T
}

type SomeTypeList<T extends any[]> = { [K in keyof T]: SomeType<T[K]> }

interface OtherType<T> {
  otherValue: T
}

type OtherTypeList<T extends any[]> = { [K in keyof T]: OtherType<T[K]> }

function convertValues<T extends any[]> (arr: SomeTypeList<T>): OtherTypeList<T> {
  // Is there any way to write this without the cast?
  return arr.map(({ value }) => ({ otherValue: value })) as OtherTypeList<T>
}

convertValues<[number, string, boolean]>([{ value: 1 }, { value: 'cat' }, { value: true }])
// => [{ otherValue: 1 }, { otherValue: 'cat' }, { otherValue: true }]

With the cast it loses type safety, as the callback could instead return ({ otherValue: 1 }) and everything would type check properly.

解决方案

No, this is not possible in TypeScript as of TS4.1. There are two issues I see; one might be overcome with a change to the TypeScript standard library typings for Array.prototype.map(), but the other would require a fairly large change to the type system to work in general and cannot currently be handled in a way that isn't, in some sense, equivalent to a type assertion (what you're calling a "cast").


The current library typings for the map() method of an array is:

interface Array<T> {
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}

The return type, U[] is an unordered array type corresponding to the output type of the callbackfn argument. It is not a tuple. There is an open GitHub issue, microsoft/TypeScript#29841 asking to change this so that when you call map() on a tuple, you get a tuple of the same length out. It is not currently implemented; but you can use declaration merging to test such a change out for yourself:

interface Array<T> {
  map<This extends Array<T>, U>(this: This, fn: (v: T) => U): { [K in keyof This]: U }
}

If I call convertValues() (with a change to the arr parameter as a variadic tuple type which gives the compiler a hint that it should interpret arr as a tuple if possible), you can see how it now knows how many elements are in the return value:

function convertValues<T extends any[]>(arr: [...SomeTypeList<T>]) {
  return arr.map(({ value }) => ({ otherValue: value }))
}

const ret = convertValues([{ value: 1 }, { value: 'cat' }, { value: true }])
ret[0]; ret[1]; ret[2]; // okay
ret[3] // error! Tuple type of length '3' has no element at index '3'.

Unfortunately, the types of each tuple element are not known:

ret[0].otherValue.toFixed(2); // error!
// -------------> ~~~~~~~
// Property 'toFixed' does not exist on type 'string | number | boolean'.

Each element is only known to the compiler to be {otherValue: string | number | boolean}. This isn't incorrect, but also isn't what you're looking for.


So let's step back and think about what you'd need map() to be like for this to do what you want. When you call arr.map(), you're thinking of the callback as a generic function that turns an input of a generic type SomeType<V> to an output of type OtherType<V>, for any V. Otherwise there's no chance of getting the compiler to notice the correlation between each element of the input tuple and the corrsponding element of the output tuple. You can indeed write that when you call map() by annotating the callback:

function convertValues<T extends any[]>(arr: [...SomeTypeList<T>]) {
  return arr.map(<V,>(
    { value }: SomeType<V>
  ): OtherType<V> => ({ otherValue: value }))
}

The problem is... how do you describe such generic callbacks in general which might do something else? I presume you don't want to hardcode the typing for map() to care about callbacks that specifically turn SomeType<V> into OtherType<V>. You want to say "callbacks that turn F<X> into G<X> for any F and G":

// not valid TypeScript, don't use it
interface Array<T> {
  map<A extends any[], F<?>, G<?>>(
    this: { [K in keyof A]: F<A[K]>},
    fn: <V>(v: F<V>) => G<V>
  ): { [K in keyof A]: G<A[K]> }
}

But there is no way to express this in TypeScript. F and G above are not generic types, but generic type functions or type constructors. And these do not exist in TypeScript. Generic type constructors would require introduction of so-called higher kinded types as can be found in some more functional-programming-heavy languages like Haskell and Scala. There is a longstanding open feature request, microsoft/TypeScript#1213 asking for this, but who knows if it will ever be implemented. It would be quite a bit of work, so I'm not holding my breath (but would love to see it!). and there are some possible ways to simulate higher kinded types in TypeScript (you can read that GitHub issue for more), but nothing I'd want to recommend for your use case.

So we're stuck. There's currently no way to write map()'s typing to get the compiler to verify that convertValues()'s implementation conforms to the type you claim to return.


And when the compiler can't verify that the type of something is what you claim it is, and you're confident that your claim is nevertheless correct, you pretty much need to do something like a type assertion, as you've already done. So I'd recommend you keep doing it the way you've shown here, and revisit if higher kinded types ever show up in TypeScript.

Playground link to code

这篇关于将元组类型的值映射到不同的元组类型的值而无需强制转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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