简体   繁体   English

Typescript:为类似 reducer 的函数键入组合器

[英]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子定义一个类型,使得

  1. The const reducer has type Reducer<"S", { first: "whatever", second: 5 }> const reducer的类型为Reducer<"S", { first: "whatever", second: 5 }>
  2. In the scope of the lines marked with // *1 the types of first and second are correctly inferred as first: "whatever" and second: 5在标有// *1的行的 scope 中, firstsecond的类型被正确推断为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处), firstsecond的类型没有被正确推断。 We only get first: any and second: any while it should be first: 1 and second: 2 .我们只得到first: anysecond: any而它应该是first: 1second: 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.

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