【问题标题】:How to implement generic function to instantiate TypedArrays如何实现泛型函数来实例化 TypedArrays
【发布时间】:2020-08-15 11:34:15
【问题描述】:

我正在尝试编写这样的函数:

export function createTypedArray<T extends TypedArray>( arg : { source : T, arraySize : number } ) : T {
  if( arg.source instanceof Int32Array ) {
    return new Int32Array( arg.arraySize );
  }
  if( arg.source instanceof Float32Array ) {
    return new Float32Array( arg.arraySize );
  }
  if( arg.source instanceof Float64Array ) {
    return new Float64Array( arg.arraySize );
  }
  throw 'Unsupported typed array type!';
}

问题是打字稿编译器给了我这样的错误:

类型“Int32Array”不可分配给类型“T”。 “Int32Array”可分配给“T”类型的约束,但“T”可以用约束“TypedArray”的不同子类型实例化。ts(2322)

我注意到如果我像这样添加 可以消除错误:

return <T> new Int32Array( arg.arraySize );

有没有更好的方法来编写这个函数?

有没有办法这样写:

export function createTypedArray<T extends TypedArray>( arg : { source : T, arraySize : number } ) : T {
  if( arg.source instanceof Int32Array ||
      arg.source instanceof Float32Array ||
      arg.source instanceof Float64Array
  ) {
    return new <T>( arg.arraySize );
  }
  throw 'Unsupported typed array type!';
}

【问题讨论】:

标签: typescript generics


【解决方案1】:

您的代码不起作用的原因是您对instanceof type guards 有误解。它们缩小了左侧变量的类型,在您的情况下为arg.source。类型保护不会缩小类型变量 T.*

的类型

因此,在任何情况下,Typescript 不够聪明,无法从逻辑上推断出某物的类型,但你可以,这正是你应该使用类型断言的时候。所以

return new Int32Array( arg.arraySize ) as T

正是你应该做的!


*可以说应该,因为T 必须是arg.source 的类型——也许你可以将它提交给Typescript 团队。

【讨论】:

  • 这可能不需要自己的问题; microsoft/TypeScript#13995 涵盖了缩小变量永远不会缩小泛型类型参数的情况。
【解决方案2】:

这是实现泛型类型参数是联合类型的泛型函数的一个痛点。请参阅microsoft/TypeScript#24085 了解更多信息。基本问题是,虽然编译器可能可以将arg.source 的类型缩小为Int32Array,但编译器不能缩小泛型类型从T extends TypedArrayT extends Int32Array 的参数。在一般中,当您缩小T 类型的值时,缩小T 是不安全的,但在像您这样的许多情况下,这样做会非常方便。不过目前还没有发生。

要获取现有实现并使其编译,您可以像以前一样使用type assertions


如果这就是我要说的全部内容,我会让另一个答案站得住脚,但我想表明,如果你愿意将你的实现扭曲成椒盐脆饼来引导编译器完成你的逻辑,你可以在没有断言的情况下获得至少模糊类型安全的东西:

const typedArrayMaker = (size: number) => ({
    get Int32Array() { return new Int32Array(size); },
    get Float32Array() { return new Float32Array(size); },
    get Float64Array() { return new Float64Array(size); }
});    
export function createTypedArray<K extends keyof ReturnType<typeof typedArrayMaker>>(
    arg: { source: { [Symbol.toStringTag]: K }, arraySize: number }) {
    return typedArrayMaker(arg.arraySize)[arg.source[Symbol.toStringTag]];
}

这里我们构建了一个typedArrayMaker 函数,给定一个大小,它返回一个带有getter 方法的对象,每个对象对应一个您希望能够创建的数组类型。编译器将typedArrayMaker 的类型视为

const typedArrayMaker: (size: number) => {
    readonly Int32Array: Int32Array;
    readonly Float32Array: Float32Array;
    readonly Float64Array: Float64Array;
}

那么createTypedArray()函数的参数和之前一样,但是泛型参数是K,即arg.source[Symbol.toStringTag]属性的值。已知所有类型化的数组都具有该属性的 string literal 值。这就是我们用来索引typedArrayMaker 的内容。

所有这一切都将返回类型变成了一个通用的索引操作,它实际上可以推理。您最终会得到一个依赖于K 的返回类型,并且没有错误。让我们测试一下:

function test(i32: Int32Array, f32: Float32Array, f64: Float64Array, i8: Int8Array) {
    const i32New = createTypedArray({ source: i32, arraySize: 128 }); // Int32Array
    const f32New = createTypedArray({ source: f32, arraySize: 128 }); // Float32Array
    const f64New = createTypedArray({ source: f64, arraySize: 128 }); // Float64Array
    const i8New = createTypedArray({ source: i8, arraySize: 128 }); // error!
    // ----------------------------> ~~~~~~
    // Type '"Int8Array"' is not assignable to type 
    // '"Int32Array" | "Float32Array" | "Float64Array"'
}

您可以看到编译器识别出i32Newf32Newf64New 是预期类型,而i8 在传递到createTypedArray() 时会导致错误,因为该类型化数组类型没有'尚未包含在我们的函数中。

只是认为以编译器理解的方式展示一种方法会很有趣。在实践中,我会 100% 绝对推荐使用类型断言,因为高阶函数与 getters-and-symbol-properties 太疯狂了。


好的,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

  • 我很感激你为什么要这样做,因为类型断言解决方案不仅更短,更简单,不会引起头痛并且完全类型安全(即你不能提高它的类型安全性)。
  • 也许我们使用“类型安全”的方式不同。类型断言特别允许您说表达式的类型比编译器可以验证的类型更窄(即向下转换)。这将类型安全的责任从编译器转移到了开发者身上,开发者很容易对编译器撒谎(例如if (arg.source instanceof Float32Array) { return new Int32Array( arg.arraySize ) as T }),编译器也不会报错。一个认真的函数实现者可以确保调用者具有类型安全性,但自己没有这种安全性。
  • 另外我会小心声称“彻底”类型安全。我经常后悔提出这样的要求。例如,class I32WithCheese extends Int32Array { cheese = "cheddar"; }Int32Array 的一个子类,如果你使用一个:const i = new I32WithCheese([]); const a = createTypedArray({ source: i, arraySize: 128 }); 你有一个值 a 编译器错误地认为有一个 cheese 属性,因为类型断言:a.cheese.toUpperCase() 在运行时出错,但没有给你编译器警告。 Oops.
  • 我的立场是正确的。请编辑您的答案,以便我更改投票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-03
相关资源
最近更新 更多