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)
)
)
);
};
如果 tableExists
和 createTable
的错误类型不同,您正确地确定需要使用 chainW
。 fp-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
我正在学习一些 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)
)
)
);
};
如果 tableExists
和 createTable
的错误类型不同,您正确地确定需要使用 chainW
。 fp-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)
).