[英]Typescript: Typing a combinator for reducer-like functions
Consider the following types考虑以下类型
type Reducer<S, A> = Red<S, A, A>
type Red<S, C, A> = (s: S) => (c: C) => A
and the following function以及以下 function
// The type intended for `reds` is
// `{ [k in keyof P]: Red<S, P, P[k]> }`
// for fixed `S` and `P`
const combinator: ??? = reds =>
step => acc =>
{
const result = {}
for (const key in reds) {
result[key] = reds[key](step)(acc)
}
return result
}
Now consider the following application (in ts) of this function:现在考虑这个 function 的以下应用(在 ts 中):
const reducer = combinator({
first: (s: "S") => ({ first, second }) => {
// *1
return "whatever"
},
second: (s: "S") => ({ first, second }) => {
// *1
return 5
},
})
Question: How can we define a type for the function combinator
in typescript such that问题:我们如何为 typescript 中的 function
combinator
子定义一个类型,使得
reducer
has type Reducer<"S", { first: "whatever", second: 5 }>
reducer
的类型为Reducer<"S", { first: "whatever", second: 5 }>
// *1
the types of first
and second
are correctly inferred as first: "whatever"
and second: 5
// *1
的行的 scope 中, first
和second
的类型被正确推断为first: "whatever"
和second: 5
In short: What should we put in the following code in place of ???
简而言之:我们应该在下面的代码中放置什么来代替
???
const combinator: ??? = reds => { ... }
[Remark]: I posted a partial solution. [备注]:我发布了部分解决方案。 Maybe it helps as a starting point?
也许它有助于作为一个起点?
edit:编辑:
The motivation for the combinator is to allow for parallel reducing with mutually referencing reducers.组合器的动机是允许使用相互引用的缩减器进行并行缩减。 The use case I have in mind is compiling an AST.
我想到的用例是编译 AST。 But to keep it simpler: Here's a contrived example :
但为了简单起见:这是一个人为的例子:
const combinator = reds =>
step => acc =>
{
const result = {}
for (const key in reds) {
result[key] = reds[key](step)(acc)
}
return result
}
const reduce = arr => ({ init, red }) =>
{
let acc = init
for (const step of arr) {
acc = red(step)(acc)
}
return acc
}
const exampleInput = [
"some line",
"some other line",
"yet another line",
]
const exampleReducer = combinator({
textWithLengths:
line => ({ textWithLengths, totalLength }) =>
textWithLengths + "\n" + `${totalLength} -- ${line}`,
totalLength:
line => ({ totalLength }) =>
totalLength + line.length,
startsWithSame:
line => ({ startsWithSame, previousLine }) =>
[...startsWithSame, previousLine.charAt(0) === line.charAt(0)],
previousLine:
line => _ =>
line
})
const exampleInit = {
textWithLengths: "",
totalLength: 0,
startsWithSame: [],
previousLine: "",
}
const exampleResult = reduce(exampleInput)({
init: exampleInit,
red: exampleReducer
})
console.log(exampleResult.textWithLengths)
console.log(exampleResult.startsWithSame)
The output will be output 将是
// console.log(exampleResult.textWithLengths)
0 -- some line
9 -- some other line
24 -- yet another line
[ false, true, false ]
// console.log(exampleResult.startsWithSame)
[ false, true, false ]
I think the definition of types is a little bit convoluted.我认为类型的定义有点复杂。 I don't know if you have "collateral" requirements, given the other code, but, evaluated in a vacuum, something like this could work (untested, except for type coercion):
我不知道你是否有“附带”要求,给定其他代码,但是,在真空中评估,这样的事情可能有效(未经测试,类型强制除外):
const exampleInput = [
"some line",
"some other line",
"yet another line",
]
interface Aggr {
textWithLengths: string;
totalLength: number;
startsWithSame: boolean[];
previousLine: string;
}
const exampleInit: Aggr = {
textWithLengths: "",
totalLength: 0,
startsWithSame: [],
previousLine: "",
}
type Reducers<TInput, TOutput> = {
[k in keyof TOutput]: (input: TInput, aggr: TOutput) => TOutput[k];
};
const reduce = function <TInput, TOutput>(
input: TInput[],
init: TOutput,
reducers: Reducers<TInput, TOutput>
) {
let acc = init;
const reducerNames = Object.keys(reducers);
for (const item of input) {
const newAcc: any = {};
for (const reducerName of reducerNames) {
const red = (reducers as any)[reducerName];
newAcc[reducerName] = red(item, acc);
}
acc = newAcc as TOutput;
}
return acc;
};
// types can be implicitly checked
const result = reduce<string, Aggr>(exampleInput, exampleInit, {
textWithLengths: (line, aggr) =>
aggr.textWithLengths + "\n" + `${aggr.totalLength} -- ${line}`,
totalLength: (line, aggr) => aggr.totalLength + line.length,
startsWithSame: (line, aggr) => [
...aggr.startsWithSame,
aggr.previousLine.charAt(0) === line.charAt(0),
],
previousLine: (line) => line,
});
console.log(result.textWithLengths)
console.log(result.startsWithSame)
Instead of TInput[]
, you could use another interface, like Iterator<TInput>
.您可以使用另一个接口,例如
Iterator<TInput>
,而不是TInput[]
。
Edit: I updated the code fixing some type mismatches inside reduce and tested it here .编辑:我更新了代码,修复了 reduce 中的一些类型不匹配,并在此处进行了测试。 Seems fine to me.
对我来说似乎很好。
Here's a partial solution . 这是部分解决方案。 But it's not the full answer: The problem is that at the marked places in the code (at
*1
, *2
), the types of first
and second
are not inferred correctly.但这不是完整的答案:问题是在代码中标记的地方(在
*1
, *2
处), first
和second
的类型没有被正确推断。 We only get first: any
and second: any
while it should be first: 1
and second: 2
.我们只得到
first: any
和second: any
而它应该是first: 1
和second: 2
。
type Red<S, C, A> = (s: S) => (c: C) => A
type Reducer<S, A> = Red<S, A, A>
type Target<R extends Red<any, any, any>> =
R extends Red<any, any, infer S>
? S
: ["this should not happen: could not infer target type of reducer", R]
type CombinedTarget<Reds extends { [k: string]: Red<any, any, any> }> =
{ [k in keyof Reds]: Target<Reds[k]> }
const combinator = <S, Reds extends { [k: string]: Red<S, any, any> }>(
reds: Reds
): Reducer<S, CombinedTarget<Reds>> =>
(s: S) => (p: CombinedTarget<Reds>) =>
{
const result: Partial<CombinedTarget<Reds>> = {}
for (const key in reds) {
result[key] = reds[key](s)(p)
}
return result as CombinedTarget<Reds>
}
const result = combinator({
first: (s: "S") => ({ first, second }) => {
// *1
return 1 as 1
},
second: (s: "S") => ({ first, second }) => {
// *2
return 2 as 2
},
})
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.