下面是一个最小的转换实现,但它的输出类型不正确。寻找需要改变的地方来纠正它。这是 TS Playground 中的示例,其中包含转导和传递给它的所有参数。
如果有帮助的话,这里有一个“可能更简单的示例游乐场”。我不确定它是否过于简单化了。
/* transducer */
const map =
<VOut,VIn,KIn>(mapper: (v: VIn, k?:KIn) => VOut) =>
<A, R>(nextReducer: (acc: A, v: VOut, k?:KIn) => R) =>
<V extends VIn>(acc: A, value: V, key?:KIn) => nextReducer(acc, mapper(value,key), key);
/* final reducer */
const toSet = <V,K,A>(acc:A=new Set() as A, v: V, k?:K) => {
(acc as Set<V>).add(v);
return acc as Set<V>;
};
/* test map and toSet by themselves */
// test correct - mapNum is Set<string>
const mapStr = map((v: number) => `${v}`)(toSet)(undefined,1,'a');
// test correct - mapNum is Set<number>
const mapNum = map((v: number) => v + 2)(toSet)(undefined,1,'a');
// @ts-expect-error test correct - string 'aa' passed to number mapper should error
const mapStrError = map((v: number) => v + 2)(toSet)(undefined,'aa','a');
/* transduce */
const transduce = <AccIn,AccOut,VIn,VOut,KIn,KOut>(
collection: Map<KIn, VIn>,
transducer: <
NextReducer extends (a:any,v:any,k?:any)=>any
>(nextReducer:NextReducer)=>((a:AccIn,v:VIn,k?:KIn)=>AccOut),
finalReducer: (a:AccOut|undefined,v:VOut,k?:KOut)=>AccOut,
) => {
let combinedReducer = transducer(finalReducer);
let acc = undefined as AccOut;
for (const [key, value] of collection) {
acc = combinedReducer(acc as unknown as AccIn, value, key);
}
return acc;
}
/* test transduce */
// @ts-expect-error correct - string collection passed to number mapper should error
const transduceStr = transduce(new Map([['a', 'aa']]), map((v: number) => v * 2), toSet);
// incorrect - transduceNum is unknown. Should be Set<number> like mapNum
const transduceNum = transduce(new Map([['a', 1]]), map((v: number) => v * 2), toSet);
microsoft/TypeScript#30134 对此有一个讨论。
当前的推理算法本质上是启发式的,并且针对处理各种现实世界代码进行了优化,特别是处理“部分编写”代码,以便 IDE 可以比完全统一更容易地完成和建议事情。请参阅 语言架构师在 microsoft/TypeScript#17520 上的评论。
因此,无论好坏,TypeScript 都无法按照您想要的方式推断您的泛型类型参数,并且这在未来不太可能发生重大变化。 您需要以某种方式解决该问题。一种通用方法是手动指定预期的泛型类型参数,而不是让 TypeScript 推断它们。这很乏味,特别是如果您的泛型函数具有... six... 类型参数,但其行为始终是可预测的,并让您知道是否有真正的错误:
const transduceNum = transduce<Set<number>, Set<number>, number, number, string, string>(
new Map([['a', 1]]), map((v: number) => v * 2), toSet
); // okay, Set<number> as desired, no error
默认类型参数,以便您可以指定更少的类型参数并仍然获得您想要的类型。
或者您可以使用实例化表达式将泛型函数参数(如
toSet
)转换为特定的非泛型函数:
const transduceNum = transduce(
new Map([['a', 1]]), map((v: number) => v * 2), toSet<number, string, Set<number>>
); // okay, Set<number> as desired, no error
或者您可以通过完全重构来解决问题,但这超出了所提出的问题的范围,所以我不会进一步离题。
Playground 代码链接