使用 TypeScript 和 fp-ts 进行类型建模以及使用 Either 时出现错误

Type modeling with TypeScript and fp-ts and error while using Either

我正在尝试使用 TypeScript 和 fp-ts 来尝试建模 域逻辑与类型,我遇到过这个问题:

import { left, right, Either } from "fp-ts/lib/Either";

type MyType = {
  id: string,
  isValid: boolean,
}

type MyValidType = {
  id: string,
  isValid: true,
}

type CreateMyValidType = (t: MyType) => Either<Error, MyValidType>

// Compile error!
const createMyValidType: CreateMyValidType = t => {
  switch (t.isValid) {
    case true:
      return right({
        id: "test",
        isValid: true
      })
    default:
      return left(new Error())
  }
}

编译器对我大吼大叫,因为: Type '(t: MyType) => Either<Error, { id: string; isValid: boolean; }>' is not assignable to type 'Either<Error, CreateMyValidType>'.

如果我删除 Either 并且我只是 return 总和类型 Error | MyValidType 就没问题了。

type CreateMyValidType = (t: MyType) => Error | MyValidType

// This compiles
const createMyValidType: CreateMyValidType = t => {
  switch (t.isValid) {
    case true:
      return {
        id: "test",
        isValid: true
      }
    default:
      return new Error()
  }
}

Either里面好像无法识别正确的类型!

我找到了通过在调用 right 时指定类型来避免此问题的方法,但我不完全理解其中的含义,所以我不知道这是否是个坏主意:

return right<Error, MyType2>({
  id: "test",
  isValid: true,
});

处理这个问题并使其编译的正确方法是什么? 谢谢!

简答

在 TS >= 3.4

下它按预期工作

稍微长一点的回答

您可能已经注意到,TypeScript 通常在推理方面并不是很好。 在您的代码示例中,您为函数 Either<Error, MyValidType> 的 return 类型提供了注释,以便 TS 可以尝试将所有分支统一为预期的 return 类型:没有此显式注释,结果会更糟。

即使使用手动类型注释,3.4 之前的 TS 也会是 "lazy" 并尝试解析由 leftright 函数声明的所有泛型类型参数(两者都有LR 作为类型参数)就位,没有 "waiting" 在做出选择之前获得更好的知识。 因此,对于 default 的情况,它将 Error 推断为 L,对于 true 的情况,将 { id: string, isValid: boolean } 推断为 R。问题是 MyValidType 要求 isValid 是文字 true(比 boolean 更具体),因此它最终以

失败
Type '{ id: string; isValid: boolean; }' is not assignable to type 'MyValidType'.
  Types of property 'isValid' are incompatible.
    Type 'boolean' is not assignable to type 'true'.

随着 TS >= 3.4R 被保留 "undecided" 直到过程的后期,当 TS 实际上知道 return 的预期(注释)类型时=32=],并正确地将文字对象视为可分配给声明的 return 类型。

您可以在 https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#higher-order-type-inference-from-generic-functions

的官方更新日志中阅读有关此改进的更多信息

注一

这个问题与 fp-ts 没有真正的关系,因为任何通用函数在 3.4 之前都会出现类似的问题:

declare function identity<T>(t: T): T;

function f(): { foo: 'foo' } {
  return identity({ foo: 'foo' });
}
// Type '{ foo: string; }' is not assignable to type '{ foo: "foo"; }'.
//   Types of property 'foo' are incompatible.
//     Type 'string' is not assignable to type '"foo"'.

注2

查看此示例的另一种方式是,默认情况下 TS 不会推断出最精确的可能文字类型,某些特定情况除外:

const foo = 'foo' // Type: "foo"

const fooObj = { foo: 'foo' } // Type: { foo: string }

这是 "safe" 考虑到 JS 可变性的默认设置。可以使用“const 断言”更改此行为:

const fooObj = { foo: 'foo' } as const // Type: { readonly foo: "foo" }

这是 3.4 中的另一个补充(参见 https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#const-assertions),由于 createMyValidType 中的 return 类型注释,您的示例中并不严格需要它.