Как реализовать функцию generi c для создания экземпляров TypedArrays - PullRequest
1 голос
/ 01 мая 2020

Я пытаюсь написать такую ​​функцию:

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!';
}

Ответы [ 2 ]

0 голосов
/ 01 мая 2020

Это трудная задача при реализации обобщенных функций c, где параметр типа generi c имеет тип объединения. См. microsoft / TypeScript # 13995 для получения дополнительной информации. Основная проблема c заключается в том, что, хотя может иметь возможность для компилятора сузить тип arg.source, скажем, Int32Array, компилятор не может сузить генерит c введите параметр от T extends TypedArray до чего-то вроде T extends Int32Array. В general сужать T небезопасно, когда вы сужаете значение типа T, но во многих случаях, подобных вашему, было бы очень удобно сделать это. Пока что этого не происходит.

Чтобы взять существующую реализацию и заставить ее скомпилировать, вы можете использовать утверждения типа так, как вы это сделали.


Если бы это было все, что я собирался сказать, я бы оставил другой ответ в силе, но я хотел бы показать, что если вы хотите превратить свою реализацию в крендельки для руководства компилятором через вашу логику c вы можете получить что-то, что по крайней мере неопределенно безопасно для типов без утверждения:

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() принимает тот же аргумент, что и раньше, но параметр generi c равен K, значение [Symbol.toStringTag] свойство arg.source. Известно, что все типизированные массивы имеют такое значение строковый литерал для этого свойства. И это то, что мы используем для индексации в typedArrayMaker.

Все это превращает возвращаемый тип в обобщенную c индексацию операцию, которую он может на самом деле рассуждать. В итоге вы получите тип возврата, который зависит от 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"'
}

Вы можете видеть, что компилятор распознает, что i32New, f32New и f64New относятся к ожидаемым типам, тогда как i8 вызывает ошибку при передаче в createTypedArray(), поскольку этот типизированный тип массива еще не включен в нашу функцию.

Просто подумал, что было бы интересно показать один способ сделать это так, как понимает компилятор. На практике я бы на 100% определенно рекомендовал использовать утверждение типа, потому что свойства функций более высокого порядка с получателями и символами безумны.


Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

0 голосов
/ 01 мая 2020

Причина, по которой ваш код не работает, заключается в том, что вы неправильно понимаете охранники типа instanceof . Они сужают тип переменной с левой стороны, который в вашем случае равен arg.source. Защита типа НЕ сужает тип переменной типа T. *

Так что в любой ситуации, когда Typescript недостаточно умен, чтобы логически определить тип чего-либо, , но вы можете именно тогда вы должны использовать утверждения типа. Так что

return new Int32Array( arg.arraySize ) as T

- это точно то, что вы должны делать!


* Возможно, так и должно быть, поскольку T должно быть типа arg.source - возможно, вы можете отправить это команде Typescript.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...