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!
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.