1

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

简而言之,看看表是否存在,如果不存在,就创建它——在整个过程中都有可能出错。关键是我希望这两个错误都反映在最外面的类型中: 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()))
  );
};
4

2 回答 2

1

我相信我已经弄清楚了,尽管当然欢迎任何更正。

与其TaskEither从. Option_ 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.chainWreplaces TE.chain

于 2021-04-26T20:51:37.340 回答
1

看起来你自己想出来了 :) 如果它有帮助,我已经将带有错误的示例实现为可区分的联合,以便您可以轻松识别调用example.

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)
      )
    )
  );
};

chainW如果错误类型来自tableExistscreateTable不同,您正确地确定了您需要使用。Win 函数末尾的意思fp-ts是“扩大”,它通常允许类型扩大到两种类型的联合。在 for 的情况下chainWTaskEither这意味着错误类型将成为两种TaskEither类型的并集(一种进入chainW,一种在其中返回)。

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

于 2021-04-26T21:13:34.207 回答