Búsqueda de sitios web

typescript1.8 - ¿Fallo de inferencia de tipos en Typescript?


Escribí una función para proyectar un Array en un Map:

function toMap<T,TKey,TElement>(items: Array<T>,
        keySelector: (item: T) => TKey,
        elementSelector: (item: T) => TElement
    ): Map<TKey,TElement> {

    var map = new Map<TKey,TElement>();
    for (var item of items) {
        map.set(keySelector(item), elementSelector(item));
    }
    return map;
}

Básicamente, recorre la lista y llama a una función de proyección proporcionada por el usuario para extraer una clave y un valor para el Mapa.

Tenga en cuenta que los tipos TKey y TElement se infieren a partir del valor de retorno de las funciones de proyección que se pasan.

La mayoría de las veces, el valor en el mapa es simplemente el elemento original:

var personBySocial = toMap(people, person => person.ssn, person => person);

Así que me gustaría hacer que esa segunda lambda sea la predeterminada. Suficientemente fácil:

function toMap<T,TKey,TElement>(items: Array<T>,
        keySelector: (item: T) => TKey,
        elementSelector: (item: T) => TElement = item => item
    ): Map<TKey,TElement> {

    var map = new Map<TKey,TElement>();
    for (var item of items) {
        map.set(keySelector(item), elementSelector(item));
    }
    return map;
}

Sin embargo, esto produce un error de compilación:

Type '(item: T) => T' is not assignable to type '(item: T) => TElement'.
  Type 'T' is not assignable to type 'TElement'.

Por alguna razón, si paso item => item por separado, Typescript puede inferir que TElement == T, pero si lo paso como valor predeterminado, no puede.

¿Estoy haciendo algo mal aquí, o es esta una limitación actual de Typescript? Si es esto último, ¿alguien sabe si es un problema conocido que se abordará en el futuro?

EDITAR, en respuesta al comentario de Ryan:

function toMap<T,TKey>(items: Array<T>, keySelector: (item: T) => TKey): Map<TKey,T>;
function toMap<T,TKey, TElement>(items: Array<T>, keySelector: (item: T) => TKey, elementSelector: (item: T) => TElement): Map<TKey, TElement>;
function toMap<T,TKey, TElement>(items: Array<T>, keySelector: (item: T) => TKey, elementSelector?: (item: T) => TElement): Map<TKey, TElement> {
    var map = new Map<TKey, TElement>();
    if (elementSelector)
        for (var item of items)
            map.set(keySelector(item), elementSelector(item));
    else
        for (var item of items)
            map.set(keySelector(item), item);
    return map;
}

Me estoy encontrando con el mismo tipo de error, donde el map.set en la cláusula else (map.set(keySelector(item), item)) está causando el error en el segundo item que "T no se puede asignar al parámetro de tipo TElement".

Respuesta #1

Creo que probablemente te convenga más escribir esto como dos firmas:

function toMap<T,TKey>(items: Array<T>, keySelector: (item: T) => TKey): Map<TKey,T>;
function toMap<T,TKey,TElement>(items: Array<T>, keySelector: (item: T) => TKey, elemSelector: (item: T) => TElement): Map<TKey,TElement>;
/* implementation signature here */

Esto sería solucionable con valores predeterminados de tipos genéricos (escribirías <T, TKey, TElement=T>), pero esos aún no se han fusionado.

La queja legítima sobre tu código, desde la perspectiva del compilador, es que alguien podría especificar manualmente todos los parámetros de tipo, pero no especificar el argumento opcional:

let x = toMap<string, number, boolean>(someStringArray, someNumberFunc);