我正在学一些fp-ts
。要创建我正在处理的问题的样式化版本,假设我想要创建一个不存在的表,所以我必须查询数据库:一个易出错的异步操作。如果该表不存在,我希望创建它:另一个错误的异步操作。进一步假设错误类型都是字符串(如果需要的话,我还想知道如何创建一个联合错误类型),并且成功创建时返回的值是一个数字ID。
简而言之,看看表是否在那里,如果没有,就创建它--在整个过程中都有出错的可能。关键是我希望两个错误都反映在最外层的类型:一个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()))
);
};
发布于 2021-04-26 12:51:37
我想我想明白了,当然欢迎任何更正。
与其将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.chainW
代替了TE.chain
。
发布于 2021-04-26 13:13:34
似乎您自己解决了这个问题:)如果有帮助,我已经将错误实现为一个受歧视的联合,这样您就可以很容易地识别在调用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
和createTable
的错误类型不同,您正确地识别了需要使用createTable
。W
在fp-ts
中函数末尾的意思是“加宽”,它通常允许将类型扩大到两种类型的合并。对于chainW
for TaskEither
,这意味着错误类型将成为两个TaskEither
类型(进入chainW
的类型和在其中返回的类型)的联合。
理解何时使用map
和何时使用chain
是一个重要的基本概念,这对于很好地理解非常重要。map
允许您修改结构中的值,它是来自A -> B
的一个简单函数。chain
允许您执行另一个依赖于第一个结果的效果,因此您必须返回一个值,该值由您所处理的相同效果包装。在本例中,您使用的是TaskEither
,因此传递给chain
的函数也需要类型为A -> TaskEither<E, B>
( createTable
是,但也需要手动处理表已经存在的情况,并使用TE.right(O.none)
或TE.of(O.none)
在那里构造TaskEither )。
https://stackoverflow.com/questions/67229271
复制