[英]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)
)
)
);
};
如果tableExists
和createTable
的错误类型不同,您正确地确定了需要使用chainW
。 fp-ts
中 function 末尾的W
表示“加宽”,它通常允许类型加宽为两种类型的并集。 对于chainW
的TaskEither
,这意味着错误类型将成为两种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.