我们能否使函数类型依赖于(派生自?)其函数类型(调用时)?

Can we make functions type dependant (derived from?) on its function types (when called)?

假设我们有一组复杂的函数:

    interface Succes<a> {
      kind: 'succes'
      value: a
    }
    interface Failure<e> {
      kind: 'failure'
      error: e
    }

    type Result<a, e> = Succes<a> | Failure<e>

    const unit = <a>(a:a): Succes<a> => ({kind: 'succes', value: a})
    const fail = <e>(e:e): Failure<e> => ({kind: 'failure', error: e})

    interface fun<a, b> { (a: a): b }

    const map = <a,b, e>(f: fun<a, b>): fun<Result<a,e>, Result<b,e>> => r => 
      r.kind == 'succes' ? unit(f(r.value)) : r

    const join = <a, e>(r:Result<Result<a, e>, e>): Result<a, e> => 
      r.kind == 'failure' ? r : r.value

    const then = <a, b, e>(f:fun<a, Result<b, e>>) => (r:Result<a, e>) => 
        join(map(f)(r))

    const railRoad = <a, e>(r: Result<a, e>) => ({
      map: <b>(f: (a:a) => b) => railRoad<b, e>(map<a, b, e>(f)(r)),
      then: <b>(f: (a:a) => Result<b,e>) => railRoad(then(f)(r))
    })

突出显示的位是最后 railRoad 位。 我们可以这样使用:

railRoad<User, *somePossibleErrors*>(getUser())
  .then(formatName)
  .map(greet)

这有一些不错的可能性,它允许我们处理任意长度的函数管道中的所有错误,但在此 - 我们必须指定我们必须处理的错误集。我希望 railRoad 中的 emap 函数派生,然后是函数 e 值。

这是我们能做的吗?因此,当我们使用 .then.map 调用函数时,它们的签名会添加到原始函数中的类型 (railRoad)?

此代码的 TS 游乐场是 here

在接下来的内容中,我将使用 TS 类型参数命名约定(大写字母),并且我已将所有地方的 "succes" 更改为 "success"。如果您愿意,可以忽略这些外观变化。我保留了将 "success" 类型称为 AB 的惯例,并将 "failure" 类型称为 EF。我承认 F 对于错误类型来说不是一个好名字,但是与 A/B 平行的字母对我来说太诱人了。


主要问题可能是你的then()函数和你的railRoad()函数没有扩展错误类型的概念。这是使用 then() 的一种方法:

const then = <A, B, F>(f: fun<A, Result<B, F>>) => <E>(r: Result<A, E>) =>
  join(map<A, Result<B, E | F>, E>(f)(r))

这里的初始函数采用将 A 变成 Result<B, F> 的东西,然后它采用 Result<A, E>,并产生 Result<B, E | F>EF 合并为 E | F 是让您的推理在以后发挥作用的关键因素。


第二个问题是您的 railRoad() 函数使用了 TypeScript 3.4 中添加的 higher order generic type inference support,但特定的公式导致编译器性能很差。函数的推断类型是递归类型,如 {map: () => {map: () => ....。为了防止我创建了一个接口 RailRoaded<A, E> 来表示 return 类型的 railRoad():

interface RailRoaded<A, E> {
  map<B>(f: (some: A) => B): RailRoaded<B, E>;
  then<B, F>(f: (some: A) => Result<B, F>): RailRoaded<B, E | F>;
}

const railRoad = <A, E>(r: Result<A, E>): RailRoaded<A, E> => ({
  map: <B>(f: (some: A) => B) => railRoad<B, E>(map<A, B, E>(f)(r)),
  then: <B, F>(f: (some: A) => Result<B, F>) => railRoad(then(f)(r))
})

railRoad 的 return 类型注释为 RailRoaded<A, E> 可以显着提高性能,因为编译器只需要 验证 该函数是兼容并且不会尝试为其合成新的 return 类型。无论如何,您还可以看到 RailRoaded<A, E>then() 方法如何生成联合类型的失败类型。


差不多就这些了。调用它时会发生以下情况:

const chooChoo = railRoad(getUser()).then(formatName).map(greet);
// const chooChoo: RailRoaded<string, "no-user" | "no-name">

看起来不错,我觉得。只是为了确定,让我们将其分开以查看每个步骤的推论:

const engine = railRoad(getUser());
// const engine: RailRoaded<User, "no-user">
const car = engine.then(formatName);
// const car: RailRoaded<string, "no-user" | "no-name">
const caboose = car.map(greet);
// const caboose: RailRoaded<string, "no-user" | "no-name">

还不错。好的,希望对您有所帮助;祝你好运!

Playground link to code