繁体   English   中英

fp-ts:如何将嵌套的`Either`/`TaskEither`“拉起”到外部类型?

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

我正在学习一些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()))
  );
};

看起来你自己想出来了:) 如果它有帮助,我已经将带有错误的示例实现为可区分的联合,以便您可以轻松识别调用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)
      )
    )
  );
};

如果tableExistscreateTable的错误类型不同,您正确地确定了需要使用chainW fp-ts中 function 末尾的W表示“加宽”,它通常允许类型加宽为两种类型的并集。 对于chainWTaskEither ,这意味着错误类型将成为两种TaskEither类型的并集(一种进入chainW ,另一种在其中返回)。

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

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

我认为我需要将Option向下“推”到嵌套的TaskEither中,以便嵌套将TaskEither的层彼此相邻,从而允许通过chain将它们展平,而不是从Option中“拉”出TaskEither

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之外,我关于如果错误类型不同我会做什么的问题似乎也由这段代码处理。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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