简体   繁体   中英

Iterable<Iterable<T>> cannot confirm generic T in function

Here is my problem.

const iterable = [[[1,2,3]]]
function flat<T>(t:Iterable<Iterable<T>>):Iterable<T>{
    return [...t][0]
}
const flatted = flat(iterable) //return Iterable<unknown>  

above function cannot suppose T as number, just assert it as unknown. At this momemt, I tought that 'hmm... generic in generic cannot be infered?'. But below code chuck works well

const iterable = [[[1,2,3]]]
function flat<T>(t:Array<Array<T>>):Array<T>{
    return [...t][0]
}
const flatted = flat(iterable) // omg.. return Array<number> 

also

const iterable = [[[1,2,3]]]
function flat<T>(t:Iterable<Array<T>>):Array<T>{
    return [...t][0]
}
const flatted = flat(iterable) // also works.. return Array<number> 

What are differences between those? Thanks for reading my question.

Yuck, yeah, I see that the default inference doesn't work deeply enough to unroll Iterable<Iterable<T>> into T . It's not that surprising if you look at how the typings for Iterable are defined in the relevant library :

interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>;
}

An Iterable<T> has a symbol-keyed method whose return type is Iterator<T> , which itself is defined as:

interface Iterator<T, TReturn = any, TNext = undefined> {
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return?(value?: TReturn): IteratorResult<T, TReturn>;
    throw?(e?: any): IteratorResult<T, TReturn>;
}

where all the methods return IteratorResult<T, ...> , which is defined to be a discriminated union type

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

whose members are

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

of which only one has a value property of the relevant type T you're trying to find.

So to turn a type X of Iterable<T> into T , the compiler needs to do something like Extract<ReturnType<ReturnType<X[I]>['next']>, { done?: false }>['value'] (where I is a fictitious indexable [Symbol.iterator] property, which we can't write ourselves due to a bug in TypeScript; microsoft/TypeScript#24622 ).

I think there's probably some depth limit after which the compiler gives up trying to infer things. You can see that inferring T from Iterable<T> works (maybe about 5 or 6 layers of nesting), but inferring T from Iterable<Iterable<T>> is just too deep (10 or 12 layers?):

type N = number[][] extends Iterable<infer T> ? T : never; // number[] 👍
type O = number[][] extends Iterable<Iterable<infer T>> ? T : never; // unknown 👎

That leads me to the following workaround: make a type alias to explicitly operate on one layer of Iterable , and then use that twice:

type DeIterable<T extends Iterable<any>> = T extends Iterable<infer U> ? U : never;

You can see this works:

type Okay = DeIterable<DeIterable<number[][]>>; // number 👍

And now flat() can be defined like this:

function flat<II extends Iterable<Iterable<any>>>(
  t: II
): Iterable<DeIterable<DeIterable<II>>> {
  return [...t][0]
}

Where the input value is the generic type II , and we use DeIterable on it twice to get the T you wanted before:

const flatted = flat(iterable) //return Iterable<number[]>  

Looks good, now, Okay; hope that helps; good luck!

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM