ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

fp-ts ReaderEither 模块完全指南:组合 Reader 依赖注入与 Either 错误处理

fp-ts ReaderEither 模块完全指南:组合 Reader 依赖注入与 Either 错误处理 开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载导读ReaderEither是 fp-ts 中把Reader从环境中读取配置/依赖的函数式能力与Either可中断的类型化错误通道组合起来的核心数据类型。它让你可以编写读取配置 → 执行可能失败的运算的纯函数式代码错误以Left值显式传递而无需抛出异常依赖以参数形式注入便于测试。读完本文你将掌握ReaderEitherR, E, A的模型、全套构造器与转换器、do notation 链式写法、错误处理策略含 Validation 聚合模式以及数组遍历等实战技能。1. 模型ReaderEitherR, E, A是什么ReaderEither的定义极其简洁见 src/ReaderEither.tsexport interface ReaderEitherR, E, A extends ReaderR, EitherE, A {}它本质上是一个先读环境、再出结果的函数R环境类型Environment即依赖/配置的类型运行时通过调用ma(config)注入E错误类型Error失败时产生LeftEA成功值类型Success成功时产生RightA。由于它继承自ReaderR, EitherE, A调用一个ReaderEither时必须传入环境值返回EitherE, A。这使得依赖注入和错误处理被显式地编码在类型里只要函数签名中出现ReaderEitherR, E, A调用方就知道它需要什么样的配置、可能以什么方式失败。与之配套的类型 lambda 定义在 src/ReaderEither.tsexport const URI ReaderEither export type URI typeof URI并通过URItoKind3注册到 HKT 系统中src/ReaderEither.ts因此它可以在泛型编程中以FR, E, A的 kind 形式参与组合。2. 构造器如何创建 ReaderEither 值文档的constructors分类提供了一组入口均自 v2.0.0 起提供of为 v2.8.5函数签名简化作用rightR, E never, A never(a: A) ReaderEitherR, E, A直接放入成功值忽略环境leftR, E never, A never(e: E) ReaderEitherR, E, A直接放入错误值忽略环境ofR unknown, E never, A never(a: A) ReaderEitherR, E, Aright的别名Pointed/Monad 的ofrightReaderR, E never, A never(ma: ReaderR, A) ReaderEitherR, E, A把 Reader 的成功结果提升为RightleftReaderR, E never, A never(me: ReaderR, E) ReaderEitherR, E, A把 Reader 的返回值作为错误放入LeftaskR, E never() ReaderEitherR, E, R读取整个环境作为成功值返回asksR, A, E never(f: (r: R) A) ReaderEitherR, E, A从环境投影出一个值作为成功值asksReaderEitherR, E, A(f: (r: R) ReaderEitherR, E, A) ReaderEitherR, E, A效果化地访问环境f返回 ReaderEitherasksReaderEitherWR1, R2, E, A(f: (r1: R1) ReaderEitherR2, E, A) ReaderEitherR1 R2, E, AasksReaderEither的宽松版本环境类型取交集合并ask与asks是依赖注入的核心例如在文档示例docs/modules/ReaderEither.ts.md与源码tapEither的用例中RE.asknumber()读取环境中的最小长度配置再交给后续校验逻辑使用。从源码看这些构造器大多是对EitherTsrc/EitherT.ts的薄封装例如 src/ReaderEither.tsexport const left: R, E never, A never(e: E) ReaderEitherR, E, A ET.left(R.Pointed) export const right: R, E never, A never(a: A) ReaderEitherR, E, A ET.right(R.Pointed) export const rightReader ET.rightF(R.Functor) export const leftReader ET.leftF(R.Functor)3. 转换与 Either、Option、Reader 互操作conversions分类提供了 4 个转换函数fromEitherv2.0.0E, A, R unknown(fa: EitherE, A) ReaderEitherR, E, A把Either原样提升进 Reader 环境。从源码看它就是R.ofsrc/ReaderEither.ts即忽略环境、返回该 Either。fromOptionv2.0.0E(onNone: LazyArgE) A, R unknown(fa: OptionA) ReaderEitherR, E, ASome变RightNone时调用onNone惰性生成错误。fromReaderv2.11.0R, A, E never(fa: ReaderR, A) ReaderEitherR, E, A把不会失败的 Reader 提升为永不失败E never的 ReaderEither。toUnionv2.10.0R, E, A(fa: ReaderEitherR, E, A) ReaderR, E | A把EitherE, A的左右两边压平成联合类型便于丢弃错误语义、只取要么错误要么值。测试 test/ReaderEither.ts 验证了fromOption的行为O.none经fromOption(() none)后调用({})得到E.left(none)O.some(1)得到E.right(1)。4. 模式匹配match / matchE / foldpattern matching分类解决如何消费 ReaderEither的问题函数说明matchv2.10.0E, B, A(onLeft: (e: E) B, onRight: (a: A) B) R(ma) ReaderR, B两个处理器返回普通值最终得到一个 ReadermatchWv2.10.0match的宽松版两个处理器返回值可以不同结果类型合并为B \| CmatchEv2.10.0后缀E表示Effect处理器返回ReaderR, B用于在分支内继续执行效果matchEWv2.10.0matchE的宽松版环境与返回类型分别取交集/合并foldv2.0.0matchE的别名历史命名foldWv2.10.0matchEW的别名从源码看这些函数通过ET.match(R.Functor)、ET.matchE(R.Monad)组合实现src/ReaderEither.tsfold matchE、foldW matchEW是直接的函数别名。5. 映射与副作用map / as / asUnit / flap / tap 家族5.1 成功值映射mapv2.0.0A, B(f: (a: A) B) R, E(fa) ReaderEitherR, E, B只映射Right分支。asv2.16.0把Right值替换为指定常量。asUnitv2.16.0把Right值替换为void。flapv2.10.0A(a: A) R, E, B(fab: ReaderEitherR, E, (a: A) B) ReaderEitherR, E, B把值应用到容器内的函数上函数在上下文中。测试 test/ReaderEither.ts 展示了map的 pipeable 用法pipe(_.right(1), _.map(U.double))({})得到E.right(2)。5.2 错误通道映射mapErrorv2.16.0R, E, G(f: (e: E) G) A(self) ReaderEitherR, G, A只映射Left分支常用于把底层错误转换为业务错误类型。文档给出了完整示例ReaderEither.mapError(ReaderEither.left(err), f)({})得到Either.left(new Error(err))其中f (s: string) new Error(s)。mapLeftv2.0.0mapError的别名legacy。mapBothv2.16.0E, G, A, B(f: (e: E) G, g: (a: A) B) R(self) ReaderEitherR, G, B同时映射失败与成功两个通道。文档示例ReaderEither.mapBoth(ReaderEither.right(1), f, g)({})得Either.right(2)ReaderEither.mapBoth(ReaderEither.left(err), f, g)({})得Either.left(new Error(err))。bimapv2.0.0mapBoth的别名legacy。在源码中bimap mapBoth、mapLeft mapError均为直接别名src/ReaderEither.ts测试 test/ReaderEither.ts 覆盖了mapLeft与bimap的左右分支行为。5.3 tap 系列保留原结果、附带执行副作用tap家族的特点是顺序执行后续计算但保留第一个计算的结果类似打点/日志/审计tapv2.15.0R1, E1, A, R2, E2, _(self, f: (a: A) ReaderEitherR2, E2, _) ReaderEitherR1 R2, E1 | E2, Af返回 ReaderEither。tapEitherv2.16.0f返回EitherE2, _用于把纯 Either 校验接到流上。源码中的完整示例src/ReaderEither.tsimport * as E from fp-ts/Either import { pipe } from fp-ts/function import * as RE from fp-ts/ReaderEither const checkString (value: string) pipe( RE.asknumber(), // 从环境读取最小长度 RE.tapEither( (minLength) value.length minLength ? E.right(ok) : E.left(error) ) ) assert.deepStrictEqual(checkString()(1), E.left(error)) // : 长度 0 1 为假 → 失败 assert.deepStrictEqual(checkString(fp-ts)(2), E.right(2)) // fp-ts: 长度 5 2 为真 → 保留原环境值 2注意tapEither失败时结果是E.left(error)而成功时保留的是第一个计算的结果环境值2这正是 tap窥探 的语义。tapReaderv2.16.0f返回ReaderR2, _用于附带执行一个只读环境、不会失败的副作用计算。6. 顺序组合flatMap 系列与 do notation6.1 flatMap 与 flattenflatMapv2.14.0支持>export declare const flatMap: { A, R2, E2, B(f: (a: A) ReaderEitherR2, E2, B): R1, E1( ma: ReaderEitherR1, E1, A ) ReaderEitherR1 R2, E1 | E2, B R1, E1, A, R2, E2, B(ma: ReaderEitherR1, E1, A, f: (a: A) ReaderEitherR2, E2, B): ReaderEither R1 R2, E1 | E2, B }它的类型揭示了组合规律环境取交集R1 R2两边都需要满足错误取并集E1 | E2任一步都可能失败。flatMap的专用变体可以接入其他单子风格的计算flatMapEitherv2.15.0f: (a: A) EitherE2, B。flatMapOptionv2.15.0f: (a: A) OptionBNone时用onNone生成错误。flatMapNullablev2.15.0f: (a: A) B | null | undefined空值时用onNullable生成错误。flatMapReaderv2.16.0f: (a: A) ReaderR2, B把纯 Reader 接入链中。flattenv2.0.0/flattenWv2.11.0则是嵌套展开flattenW接收ReaderEitherR1, E1, ReaderEitherR2, E2, A返回ReaderEitherR1 R2, E1 | E2, Aflatten flattenW因为flattenW由flatMap(identity)实现见 src/ReaderEither.ts。测试 test/ReaderEither.ts 验证了flatten与flattenW。6.2 do notationDo / bind / bindTo / let / apSdo notation 让链式调用看起来像命令式代码。起点是Dov2.9.0ReaderEitherunknown, never, {}即空环境、不失败、空记录的初始值。bindTov2.8.0N(name: N) R, E, A(fa) ReaderEitherR, E, { readonly [K in N]: A }把当前值绑定到字段名。bindv2.8.0N, A, R, E, B(name, f: (a: A) ReaderEitherR, E, B) (ma) ...把f的结果绑定为新字段字段名不能与已有字段冲突ExcludeN, keyof A。bindWv2.8.0bind的宽松版环境与错误类型合并R1 R2、E1 | E2。letv2.13.0N, A, B(name, f: (a: A) B) ...用纯函数派生新字段不进入错误通道。apSv2.8.0N, A, R, E, B(name, fb: ReaderEitherR, E, B) (fa) ...把独立的 ReaderEither 结果绑定为新字段适用于并行组合多个独立计算。apSWv2.8.0apS的宽松版。字段类型均为readonly组合后得到只读记录。ApTv2.11.0则是空元组ReaderEitherunknown, never, readonly []作为遍历空数组时的单位元。6.3 应用式组合ap / apFirst / apSecondapv2.0.0R, E, A(fa) B(fab: ReaderEitherR, E, (a: A) B) ReaderEitherR, E, B。apWv2.8.0宽松版环境取交集、错误取并集。apFirstv2.0.0保留第一个的结果。apSecondv2.0.0保留第二个的结果。apFirstW/apSecondWv2.12.0对应宽松版。测试 test/ReaderEither.ts 验证了ap、apFirst、apFirstW、apSecond、apSecondW的 pipeable 用法。7. 错误处理alt / orElse / tapError / getOrElse7.1 失败回退altv2.0.0R, E, A(that: () ReaderEitherR, E, A) (fa) ...当fa失败时改用that()的结果that惰性求值。测试 test/ReaderEither.ts 验证right(a)走alt后仍为right(a)left(1)走alt后变为right(b)。altWv2.9.0宽松版环境、错误、返回类型全部合并。orElsev2.0.0E1, R, E2, A(onLeft: (e: E1) ReaderEitherR, E2, A) (ma) ...失败时用错误值计算新的 ReaderEither。orElseWv2.10.0宽松版。orLeftv2.11.0E1, R, E2(onLeft: (e: E1) ReaderR, E2) A(fa) ...失败时用错误值生成一个Reader作为新的错误通道。orLeftWv2.16.6宽松版环境取交集。7.2 窥探失败tapErrortapErrorv2.15.0effectfully peeks at the failure——失败时执行副作用计算但保留原错误export declare const tapError: { E1, R2, E2, _(onLeft: (e: E1) ReaderEitherR2, E2, _): R1, A( self: ReaderEitherR1, E1, A ) ReaderEitherR1 R2, E1 | E2, A ... }适合在失败分支中记录日志、发送告警之后链仍以原错误失败。7.3 兜底收敛getOrElsegetOrElsev2.0.0E, R, A(onLeft: (e: E) ReaderR, A) (ma: ReaderEitherR, E, A) ReaderR, A把 ReaderEither 收敛为永不失败的 Reader失败时用onLeft提供默认值成功时取Right值。getOrElseWv2.6.0是宽松版onLeft的返回值可以与成功值类型不同合并为A | B。7.4 校验聚合getApplicativeReaderValidation / getAltReaderValidation默认的Applicative实例遇到多个错误时只返回第一个错误默认的Alt实例只返回最后一个错误。若想聚合所有错误需要提供SemigroupE来拼接错误getApplicativeReaderValidationE(S: SemigroupE): Applicative3CURI, Ev2.7.0让ap通过Semigroup合并错误。getAltReaderValidationE(S: SemigroupE): Alt3CURI, Ev2.7.0让alt通过Semigroup合并错误。从源码看二者分别基于E.getApplicativeValidation(S)src/ReaderEither.ts与ET.altValidation(R.Monad, S)src/ReaderEither.ts实现。这是实现表单校验收集所有字段错误等场景的标准手段。8. 过滤与筛选filterOrElse / fromPredicate / getCompactable / getFilterablefromPredicatev2.0.0E, A, B extends A(refinement, onFalse) R unknown(a: A) ReaderEitherR, E, B把纯值按谓词/类型守卫包装通过则Right否则Left(onFalse(a))。也支持只传Predicate的重载。filterOrElsev2.0.0对已有的 ReaderEither 施加谓词/类型守卫过滤不满足时用onFalse生成错误。支持Refinement重载类型收窄为B extends A与Predicate重载。filterOrElseWv2.9.0宽松版错误类型与原有错误合并为E1 | E2。getCompactableE(M: MonoidE): Compactable3CReaderEither, Ev2.10.0提供compact/separate用MonoidE决定空值时的错误。getFilterableE(M: MonoidE): Filterable3CURI, Ev2.10.0在 Compactable 之上再提供filter/filterMap/partition/partitionMap。源码基于E.getFilterable(M)与E.getCompactable(M)组合实现src/ReaderEither.ts。9. 提升lifting与 legacy 别名9.1 lifting 分类fromEitherKv2.4.0E, A extends readonly unknown[], B(f: (...a: A) EitherE, B) R unknown(...a: A) ReaderEitherR, E, B把返回 Either 的普通函数提升为返回 ReaderEither 的函数。fromReaderKv2.11.0把返回 Reader 的函数提升进 ReaderEither。fromPredicatev2.0.0见上文。liftNullablev2.15.0A, B, E(f: (...a: A) B | null | undefined, onNullable) R(...a: A) ReaderEitherR, E, NonNullableB把可能返回空值的函数提升为失败感知的函数。liftOptionv2.15.0把返回OptionB的函数提升为 ReaderEitherNone时用onNone生成错误。这些函数在源码中通过_.liftNullable(_FromEither)、_.liftOption(_FromEither)实现src/ReaderEither.ts。9.2 legacy 分类旧命名全部为别名旧名新名引入版本bimapmapBothv2.0.0chain/chainWflatMapv2.0.0 / v2.6.0chainEitherK/chainEitherKWflatMapEitherv2.4.0 / v2.6.1chainFirst/chainFirstWtapv2.0.0 / v2.8.0chainFirstEitherK/chainFirstEitherKWtapEitherv2.12.0chainFirstReaderK/chainFirstReaderKWtapReaderv2.11.0chainReaderK/chainReaderKWflatMapReaderv2.11.0mapLeftmapErrorv2.0.0orElseFirst/orElseFirstWtapErrorv2.11.0fromOptionKliftOptionv2.10.0chainOptionK/chainOptionKWflatMapOptionv2.10.0 / v2.13.2从源码看这些 legacy 导出几乎都是对新函数的直接赋值例如export const chain: ... flatMapsrc/ReaderEither.ts迁移到新命名零成本。10. 实例instances与类型类体系ReaderEither注册了一整套类型类实例均可从 src/ReaderEither.ts 直接导入供泛型代码按需取用实例版本组成Functorv2.7.0mapPointedv2.10.0ofApplyv2.10.0mapapApplicativev2.7.0mapapofChainv2.10.0mapapflatMapMonadv2.7.0mapapofflatMapMonadThrowv2.7.0Monad throwErrorAltv2.7.0mapaltBifunctorv2.7.0mapBothmapErrorFromEitherv2.10.0fromEitherFromReaderv2.11.0fromReaderthrowErrorv2.7.0即left的别名src/ReaderEither.tsMonadThrow由Monad加上throwError构成src/ReaderEither.ts。11. 遍历与数组工具traversing分类提供数组级操作把ReaderEither数组聚合成一个 ReaderEithersequenceArrayv2.9.0R, E, A(arr: readonly ReaderEitherR, E, A[]) ReaderEitherR, E, readonly A[]把一组 ReaderEither 倒转成包含结果数组的单个 ReaderEither任一项失败则整体失败。traverseArrayv2.9.0f: (a: A) ReaderEitherR, E, B映射并收集。traverseArrayWithIndexv2.9.0带索引版本。traverseReadonlyArrayWithIndexv2.11.0ReadonlyArray版本的带索引遍历。traverseReadonlyNonEmptyArrayWithIndexv2.11.0ReadonlyNonEmptyArray版本保证返回非空数组。源码实现是组合Reader的遍历与Either的遍历flow(R.traverseReadonlyNonEmptyArrayWithIndex(f), R.map(E.traverseReadonlyNonEmptyArrayWithIndex(SK)))src/ReaderEither.ts空数组时返回ApT。sequenceArray定义为traverseArray(identity)src/ReaderEither.ts。12. 实用工具local / swap / ap 家族localv2.0.0R2, R1(f: (r2: R2) R1) E, A(ma: ReaderEitherR1, E, A) ReaderEitherR2, E, A类似Contravariant的contramap在执行ma期间改变局部环境。适合子作用域内覆盖配置的场景源码即R.localsrc/ReaderEither.ts。swapv2.0.0R, E, A(ma: ReaderEitherR, E, A) ReaderEitherR, A, E交换错误与成功通道。ap/apW/apFirst/apFirstW/apSecond/apSecondW见 6.3 节。13. zone of death已废弃 API文档将以下 API 标记为废弃deprecated新代码不应使用readerEitherv2.0.0废弃合并了Monad、Bifunctor、Alt、MonadThrow的巨型实例。官方建议改为按需传入小实例例如如果函数只需要Functor就传RE.Functor而不是RE.readerEither。getApplySemigroupv2.0.0废弃改用Apply.ts的getApplySemigroup。getApplyMonoidv2.0.0废弃改用Applicative.ts的getApplicativeMonoid。getSemigroupv2.0.0废弃改用getApplySemigroup。getReaderValidationv2.3.0废弃改用getApplicativeReaderValidation与getAltReaderValidation两个小实例组合。14. 实战组合示例把上述能力组合起来一个典型的读取配置 → 校验 → 业务计算 → 错误兜底流程如下import * as E from fp-ts/Either import * as RE from fp-ts/ReaderEither import { pipe } from fp-ts/function // 环境应用配置 interface Config { readonly minLength: number readonly dbUrl: string } // 1. 读取配置并用 do notation 组装 const program pipe( RE.Do, RE.bind(minLength, () RE.asks((c: Config) c.minLength)), RE.let(upper, (s) s.toUpperCase()), // 纯派生 RE.bind(validated, ({ upper }) RE.filterOrElse( (s: string) s.length 2, () too-short )(RE.right(upper)) // 校验失败进入 Left ) ) // 2. 调用时注入环境得到 Either const result: E.Eitherstring, { readonly minLength: number; readonly upper: string; readonly validated: string } program({ minLength: 3, dbUrl: ... }) // 3. 失败时兜底为默认值收敛为 Reader const fallback pipe( program, RE.getOrElse((e) error: ${e}) ) fallback({ minLength: 3, dbUrl: ... })若需要聚合多个校验错误可用RE.getApplicativeReaderValidation(S.semigroupString)构造 Applicative 后与apS组合收集全部字段错误而非只取第一个。结语ReaderEither把依赖注入Reader与显式错误Either封装在一个纯函数类型中是 fp-ts 中编写可测试、类型安全业务逻辑的主力工具。掌握其模型、构造器、do notation、错误处理与 Validation 聚合模式后你可以在 src/ReaderEither.ts 与 test/ReaderEither.ts 中查阅每个 API 的实现与行为验证并配合 docs/guides/do-notation.md 与 docs/guides/HKT.md 深入理解其背后的类型类机制。赞分享开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载相关推荐如何用 Flox 和 hogli start 快速启动 PostHog 本地开发环境如何用 Flox 和 hogli start 快速启动 PostHog 本地开发环境 如果你的任务是在本机搭起一套可开发的 PostHog注意本流程面向开开发工具Newsroom数据集上的RLSeq2Seq应用预训练到强化学习的完整流程指南Newsroom数据集上的RLSeq2Seq应用预训练到强化学习的完整流程指南 想要构建一个能够自动生成高质量新闻摘要的AI系统吗RLSeq2Seq项目为你开发工具视频修复终极指南10分钟掌握untrunc开源工具拯救损坏视频视频修复终极指南10分钟掌握untrunc开源工具拯救损坏视频 你是否曾因为相机断电、存储卡故障或传输中断而丢失珍贵的视频回忆当那些承载着重要时刻的MP4、音视频视频处理上一篇Skyvern AI自动化实战指南浏览器自动化部署、原理与 API 接入步骤下一篇如何用pyRANSAC-3D轻松搞定3D点云拟合终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表