如何声明大小为 N 的元组类型(用于向量类)? [英] How to declare a tuple type of size N (to use for vector class)?

查看:22
本文介绍了如何声明大小为 N 的元组类型(用于向量类)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个具有固定大小 N 的元组类型,因此我可以执行以下操作:

I'm trying to create a tuple type, that has a fixed size N, so I can do something like this:

let tuple: Tuple<string, 2> = ["a","b"]

其中数字"是类型T,并且2"是类型T.是大小 N.然后我想创建一个(数学)Vector 类,它实现元组,并添加方法(例如向量加法),如下所示:

where "number" is the type T, and "2" is the size N. Then I want to create a (mathematical) Vector class, that implements the tuple, and adds methods (e.g. vector addition), like this:

class Vector<N extends number> implements Tuple<number,N> {...}

我确实找到了很多解决方案(用于元组类型的实现),但都存在一些问题.我找到的最简单的解决方案(此处)正在使用一个这样的界面:

I did find many solutions (for the implementation of the tuple type), but all had some problems. The simplest solution I found (here) was using an interface like this:

interface Tuple<T, N extends number> extends ArrayLike<T> //or Array<T> {
   0: T
   length: N
}

但是这个实现有一个问题,编译器让我访问无效索引处的元素,就像这样:

But this implementation has the problem, that the compiler lets me access elements at invalid indices, like this:

let tuple: Tuple<number, 2> = [1,2]
tuple[4] //No compiler error, although the index is out of bounds.

我能找到的另一个实现(这里),使用递归条件类型:

The other implementation I could find (here), is using recursive conditional types:

type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

这修复了索引问题,但我不能再在我的 Vector 类中实现这个类型,因为它是一个类型联合.我收到以下错误:

This fixes the index issue, but I can't implement this type into my Vector class anymore, because it is a type union. I get the following error:

一个类只能实现一个对象类型或对象的交集具有静态已知成员的类型.ts(2422)

A class can only implement an object type or intersection of the object types with statically known members.ts(2422)

有没有办法按照我想要的方式定义元组类型?

Is there any way to define a tuple type the way I want?

其他问题:有没有办法在没有数组类的所有方法的情况下实现元组类型?我还不确定 Vector 具有过滤、排序和其他不相关方法等方法是否有意义.特别是可以改变元组大小的方法会非常糟糕.我知道在第一个实现中使用 ArrayLike 而不是 Array 至少可以实现这一点.省略也可以,但是手动指定每个方法名称真的很烦人.

Additional question: Is there a way to implement the tuple type without all the methods from the array class? I'm not sure yet if it would make sense for a Vector to have methods like filter, sort, and other unrelated methods. Especially methods that can mutate the size of the tuple would be very bad. I know that using ArrayLike instead of Array in the first implementation would at least achieve this. Omit would work too, but it's really annoying to specify every single method name manually.

推荐答案

正如你所注意到的,编译器只允许一个 interface(或一个 class一个 interface 同名到作用域中)以静态已知";键.所以你不能扩展或实现任何键是延迟泛型的类型:

As you've noticed, the compiler will only allow an interface (or a class which brings an interface of the same name into scope) to have "statically known" keys. So you can't extend or implement any type where the keys are deferred generics:

class Foo<K extends string> implements Record<K, string> { } // error!
// A class can only implement an object type or intersection of 
// object types with statically known members.
interface Bar<K extends string> extends Record<K, string> { } // error!
// An interface can only extend an object type or intersection of 
// object types with statically known members.

对于您的 Vector 类型,您希望有从 0 到小于 N 的数字(或类似数字)键代码>.由于 NVector 的定义中是通用的,这意味着密钥的确切集合不是静态已知的(例如,是 6一个键?直到你构造一个实例你才会知道),所以你不能让 Vector 成为 classinterface>.

For your Vector<N> type, you want there to be numeric (or numeric-like) keys from 0 to one less than N. Since N is generic in the definition of Vector<N>, that means the exact set of keys is not statically known (e.g., is 6 a key? You won't know until you construct an instance), so you can't make Vector<N> a class or an interface.

如果您允许每个number值键存在,您可以使用静态已知的数字索引签名.但是您特别不希望在 N 或更大的键(或 -1Math.PI 等)处允许属性.所以让我们忘记使用索引签名.

If you allow every number-valued key to exist, you can use a statically-known numeric index signature. But you specifically don't want to allow properties at keys of N or larger (or -1 or Math.PI, etc). So let's forget about using an index signature.

您可以在此处使用的解决方法是将 Vector 实例的类型描述为 type 别名而不是 interfaceclass.您可以声明有一个名为 Vector 的值,其类型具有 构造签名 产生Vector 实例.

A workaround you can use here is to describe the types of Vector<N> instances as a type alias and not as an interface or class. You can declare that there is a value named Vector whose type has a construct signature producing Vector<N> instances.

然后你可以制作一些在运行时按你想要的方式工作的东西,但它的类型并不完全是你想要的.例如,您可以使用索引签名和 Vector 所需的所有功能制作一个 class _Vector { ... }.

Then you can make something that works the way you want at runtime, but whose types are not exactly what you want. For example, you can make a class _Vector { ... } with an index signature and all the functionality required for a Vector<N>.

最后,您可以将 _Vector 分配给名为 Vector 的变量,以及 assert 前者具有后者的类型.它基本上回避了对静态已知密钥的要求.

And finally, you can assign _Vector to the variable named Vector, and assert that the former has the type of the latter. It's basically sidestepping the requirement for statically known keys.

让我们分阶段进行:

描述类型:

从您的开始长度为 N 的元组的递归条件定义:

Starting with your recursive conditional definition for a tuple of length N:

type Tuple<T, N extends number> = N extends N ? number extends N ? 
  T[] : { length: N } & _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = 
  R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

我们将 Vector 描述为一种类型,它具有 Tuple 的所有类似数字的键,以及一个 N 的长度,以及您希望类具有的任何 Vector 特定方法或属性:

We'll describe Vector<N> as a type with all the numeric-like keys of Tuple<number, N>, as well as a length of N, and any Vector-specific methods or properties you want your class to have:

type Vector<N extends number> =
  Omit<Tuple<number, N>, keyof any[]> &
  {
    length: N;
    vectorMethod(): void;
  };

构造函数可以这样描述:

And the constructor can be described like this:

interface VectorConstructor {
  new <N extends number>(...init: Tuple<number, N>): Vector<N>;
}

实现一个在运行时工作的类:

我们将只使用索引签名:

We'll just use an index signature:

class _Vector {
  [k: number]: number | undefined;
  length: number;
  constructor(...init: number[]) {
    this.length = init.length;
    for (const [i, v] of init.entries()) {
      this[i] = v;
    }
  }
  vectorMethod() {
    console.log("something here");
  }
}

断言实现的类构造了所需的实例类型:

const Vector = _Vector as VectorConstructor;


让我们看看它是否有效:


Let's see if it works:

const v = new Vector(0, 1, 4, 9, 16, 25); // Vector<6>
/* v: {
    length: 6;
    0: number;
    1: number;
    2: number;
    3: number;
    4: number;
    5: number;
    vectorMethod: () => void;
} */

v[3]++; // okay
v.vectorMethod();
v[10]; // error

看起来不错.值 v 的类型为 Vector<6>,它具有我们想要的六个数字索引,6 类型的 length 属性,以及我们添加的 vectorMethod().因此它的行为符合预期.

Looks good. The value v is of type Vector<6>, which has the six numeric indices we want, a length property of type 6, and the vectorMethod() we added. And so it behaves as desired.

当然,这里有一些警告.显而易见的是,编译器可能不会在您的实现中捕获错误,因为您使用的是类型断言.所以你需要小心.一个不太明显的问题是您正在解决地毯下静态已知键的问题.如果有人稍后出现并尝试扩展或实现您的新类类型,他们将得到与您最初得到的相同的错误:

There are caveats here, of course. The obvious one is that the compiler might not catch errors in your implementation, because you are using a type assertion. So you need to take care. A less obvious one is that you are sweeping the problem of statically-known keys under the rug. If someone comes along later and tries to extend or implement your new class type, they will get the same error you got originally:

class Oops<N extends number> extends Vector<N> { } // error!
/* Base constructor return type 'Vector<N>' is not an object type
  or intersection of object types with statically known members. */

所以如果你想要一个类层次结构,你将需要继续使用上面的技巧,这可能会变得乏味:

So if you want a class hierarchy, you will need to keep using the above trick, which could get tedious:

class _Sigh extends _Vector {
  anotherMethod() {

  }
}
type Sigh<N extends number> = Vector<N> & { anotherMethod(): void };
interface SighConstructor { new <N extends number>(...init: Tuple<number, N>): Sigh<N>; }
const Sigh = _Sigh as SighConstructor;

游乐场链接到代码

这篇关于如何声明大小为 N 的元组类型(用于向量类)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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