fp-ts:我如何 "pull up" 嵌套的 `Either`/`TaskEither` 到外部类型?

fp-ts: How do I "pull up" a nested `Either`/`TaskEither` to the outer type?

我正在学习一些 fp-ts。要创建我遇到的问题的程式化版本,假设我想创建一个 table 如果它不存在,那么我必须查询数据库:一个容易出错的异步操作。如果 table 不存在,我想创建它:另一个容易出错的异步操作。进一步假设错误类型都是字符串(尽管我也想知道如何在需要时创建联合错误类型),并且成功创建时返回的值是数字 ID。

简而言之,查看 table 是否存在,如果不存在,则创建它——一路上都有可能出错。关键是我希望这两个错误都反映在最外层的类型中:a TaskEither<string, Option<number>>。问题是我不确定如何避免获得 TaskEither<string, Option<TaskEither<string, number>>>。也就是说,我不知道将 Option 中的错误提取出来并将其合并到最外层错误中的最佳方法。

(也许这涉及序列或遍历?我还在学习这些。)

关于一些代码:

import { taskEither as TE, option as O } from "fp-ts";
import { pipe } from "fp-ts/lib/function";

// tableExists: () => TE.TaskEither<string, boolean>
// createTable: () => TE.TaskEither<string, number>

// I want this to represent both possible errors. Currently a type error.
// -------------------------------vvvvvv
const example = (): TE.TaskEither<string, O.Option<number>> => {
  return pipe(
    tableExists(),
    // How to pull the possible `left` up to the outermost type?
    // ------------------------------------------vvvvvvvvvvvvv
    TE.map((exists) => (exists ? O.none : O.some(createTable()))
  );
};

我相信我已经弄明白了,当然欢迎任何更正。

而不是从 Option 中向上“拉”TaskEither,我想我需要将 Option 向下“推”到嵌套的 TaskEither 中,所以嵌套将 TaskEither 的层彼此相邻,允许通过 chain.

展平它们
const example = (): TE.TaskEither<string, O.Option<number>> =>
  pipe(
    tableExists(),
    TE.chain((exists) =>
      exists
        ? TE.of(O.none)
        : pipe(
            createTable(),
            TE.map(O.of)
          )
    )
  );

关于如果错误类型不同我会做什么的附带问题似乎也由此代码处理,除了 TE.chainW 替换 TE.chain

看来你是自己想出来的:) 如果它有帮助,我已经将错误的例子实现为一个可区分的联合,这样你就可以很容易地识别出调用 [=11 时发生的错误=].

import * as TE from 'fp-ts/lib/TaskEither'
import * as O from 'fp-ts/lib/Option'
import { pipe } from "fp-ts/lib/function";

declare const tableExists: () => TE.TaskEither<string, boolean>
declare const createTable: () => TE.TaskEither<string, number>

// Discriminated union so you can easily identify which error it is
type ExampleErr = { tag: "TableExistsError", error: unknown } | { tag: "CreateTableError", error: unknown }

const example = (): TE.TaskEither<ExampleErr, O.Option<number>> => {
  return pipe(
    tableExists(),
    TE.mapLeft(error => ({ tag: "TableExistsError" as const, error })),
    TE.chainW(exists => exists ?
      TE.right(O.none) :
      pipe(
        createTable(),
        TE.mapLeft(error => ({ tag: "CreateTableError" as const, error })),
        TE.map(O.some)
      )
    )
  );
};

如果 tableExistscreateTable 的错误类型不同,您正确地确定需要使用 chainWfp-ts 中函数末尾的 W 表示“扩大”,它通常允许类型扩大为两种类型的联合。在 chainW for TaskEither 的情况下,这意味着错误类型将成为两种 TaskEither 类型的并集(一种进入 chainW ,另一种进入 return在里面编辑)。

了解何时使用 map 以及何时使用 chain 是一个重要的基本概念,需要很好地理解。 map 允许您修改结构中的值,它是 A -> B 中的一个简单函数。 chain 允许您执行依赖于第一个结果的另一个效果 - 因此您必须 return 一个值,该值由您正在处理的相同效果包装。在这种情况下,您正在使用 TaskEither,因此您传递给 chain 的函数也需要是 A -> TaskEither<E, B> 类型(createTable 是,但您还需要手动处理 table 已经存在的情况,并使用 TE.right(O.none)TE.of(O.none)).

构造 TaskEither