资讯动态

fp-ts 状态与环境组合实战:StateReaderTaskEither 模块全面指南

发布时间:2026/9/23 19:31:16 来源:尧图企业网站定制
开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载StateReaderTaskEither是 fp-ts 中一个四参数S/R/E/A的“四合一”数据类型它把State可读写状态、Reader只读环境/依赖注入、Task异步、Either可失败/错误处理四种计算能力叠加在一起适合编写既需要读取配置、又需要维护内部状态、还要做异步 IO 且要求显式错误类型的业务逻辑。读完本文你将掌握该模块的模型定义、全部构造器与转换器、flatMap/tap组合家族、do notation、错误处理与状态操作并能结合 src/StateReaderTaskEither.ts 的源码理解其底层实现原理直接用于实战编码。模块定位与数据模型类型定义一个函数、四种能力在 src/StateReaderTaskEither.ts 中该类型的核心模型极其精简——它本质上是一个函数export interface StateReaderTaskEitherS, R, E, A { (s: S): ReaderTaskEitherR, E, [A, S] }含义拆解如下类型参数含义典型用法S状态类型State贯穿计算全程、可读写会话、计数器、分页游标R环境类型Reader只读、通过依赖注入提供配置对象、数据库连接池、loggerE错误类型Either 的 Left显式声明失败路径业务错误联合类型、字符串A成功值类型Either 的 Right / 计算结果查询结果、操作返回值从类型签名可以看出实现策略先吃进初始状态s: S得到一层ReaderTaskEitherR, E, [A, S]——即“读取环境 → 执行异步 → 要么失败要么返回[计算结果, 新状态]元组”。也就是说StateReaderTaskEither是把ReaderTaskEither嵌进State变换器StateT之后的产物底层对应 src/StateT.ts 中定义的StateT变换器。与相邻模块的关系去掉状态S就是ReaderTaskEithersrc/ReaderTaskEither.ts去掉异步就是ReaderEither去掉环境就是StateTaskEither源码中大量操作都是“委托”给RTE.*或ST.*完成的例如right ST.of(RTE.Pointed)、fromReaderTaskEither ST.fromF(RTE.Functor)、flatMap dual(2, ST.flatMap(RTE.Monad))见 src/StateReaderTaskEither.ts 与 src/StateReaderTaskEither.ts。这种分层设计意味着你学到的StateReaderTaskEither组合规则几乎可以平移到ReaderTaskEither、TaskEither等整个 Either 家族。状态操作get / put / modify / gets状态读写是本模块最直接的能力四个构造器全部委托自FromState实例get: S, R, E never() StateReaderTaskEitherS, R, E, S // 读出当前状态 put: S, R, E never(s: S) StateReaderTaskEitherS, R, E, void // 写入新状态 modify: S, R, E never(f: EndomorphismS) StateReaderTaskEitherS, R, E, void // 用函数变换状态 gets: S, R, E never, A never(f: (s: S) A) StateReaderTaskEitherS, R, E, A // 从状态投影出一个值在 src/StateReaderTaskEither.ts 中它们的实现分别是get_(FromState)、put_(FromState)、modify_(FromState)、gets_(FromState)即复用 fp-ts 从State到任意 monad 的“状态提升”逻辑。测试文件 test/StateReaderTaskEither.ts 对put/get/modify/gets均有断言覆盖。典型用法用get读取当前值用modify更新用gets投影派生值import * as SRTE from fp-ts/StateReaderTaskEither import { pipe } from fp-ts/function // 状态 S number每次执行 1并返回累加结果 const tick: SRTE.StateReaderTaskEithernumber, unknown, never, number pipe( SRTE.getnumber(), SRTE.flatMap((n) pipe(SRTE.modify((s: number) s 1), SRTE.map(() n))) )环境读取ask / asks / local / asksStateReaderTaskEither环境依赖注入能力来自Reader侧ask: S, R, E never() ...R——把整个环境作为结果读出asks: S, R, A, E never(f: (r: R) A) ...A——从环境投影出某个字段asksStateReaderTaskEither: R, S, E, A(f: (r: R) StateReaderTaskEitherS, R, E, A) ...——“effectfully 访问环境”即根据环境值再产生一段计算asksStateReaderTaskEitherW——asksStateReaderTaskEither的宽松版W Widening允许环境类型R1与后续计算的R2合并为R1 R2local: R2, R1(f: (r2: R2) R1) ...——在局部修改环境后执行ma类似Contravariant的contramap返回时恢复原环境。asksStateReaderTaskEitherW的源码src/StateReaderTaskEither.ts清晰展示了“吃状态 → 吃环境”的执行顺序export const asksStateReaderTaskEitherW R1, S, R2, E, A(f: (r1: R1) StateReaderTaskEitherS, R2, E, A): StateReaderTaskEitherS, R1 R2, E, A (s) (r) f(r)(s)(r)测试见 test/StateReaderTaskEither.tsasksStateReaderTaskEither与 test/StateReaderTaskEither.tslocal。实战示例——从配置环境读取数据源并执行异步查询import * as SRTE from fp-ts/StateReaderTaskEither interface Config { readonly dbUrl: string; readonly timeout: number } const program pipe( SRTE.asks((c: Config) c.dbUrl), // 读环境 SRTE.flatMap((url) SRTE.right(connect ${url})) )错误与成功构造left / right 及其 IO、Task、Reader、State 变体原文档constructors一节提供了大量“把一个别的效果放进 Left/Right”的构造器全部是 v2.0.0 引入构造器签名要点说明left(e)/right(a)直接构造失败/成功最基础的两个leftIO(me: IOE)/rightIO(ma: IOA)同步副作用产生错误/结果延迟执行运行时才执行leftTask(me: TaskE)/rightTask(ma: TaskA)异步产生错误/结果异步效果的入口leftReader(me: ReaderR, E)/rightReader(ma: ReaderR, A)依赖环境产生错误/结果leftState(me: StateS, E)/rightState(ma: StateS, A)状态计算产生错误/结果leftState取状态计算的错误值并丢弃新状态源码实现可佐证其“组合子风格”rightTask fromReaderTaskEither(RTE.rightTask(ma))、leftReader fromReaderTaskEither(RTE.leftReader(me))src/StateReaderTaskEither.ts即先把 Task/Reader 提升到ReaderTaskEither再经由ST.fromF塞进状态层。转换函数conversions把别的数据类型“搬”进来conversions一节提供从其他 fp-ts 数据类型到StateReaderTaskEither的注入通道全部为纯函数式“免费”提升转换器来源类型引入版本fromEitherEitherE, Av2.0.0fromIOIOAv2.7.0fromIOEitherIOEitherE, Av2.0.0fromOption(onNone)OptionANone时用onNone生产错误v2.0.0fromReaderReaderR, Av2.11.0fromReaderEitherReaderEitherR, E, Av2.0.0fromReaderTaskEitherReaderTaskEitherR, E, Av2.0.0fromStateStateS, Av2.10.0fromTaskTaskAv2.7.0fromTaskEitherTaskEitherE, Av2.0.0这些转换器在源码中大量采用fromReaderTaskEither(...)或ST.fromState(RTE.Pointed)等管道式实现src/StateReaderTaskEither.ts例如fromIO rightIO、fromTask rightTask直接就是构造器的别名。fromOption的签名值得注意它是柯里化的先给onNone兜底错误再接收OptionfromOption: E(onNone: LazyArgE) A, S, R unknown(fa: OptionA) StateReaderTaskEitherS, R, E, A组合与映射map / bimap / mapLeft / as / asUnit / flapmapping一节map(f)——对成功值A做变换类型保持S/R/E不变委托ST.map(RTE.Functor)src/StateReaderTaskEither.tsbimap(f, g)——同时映射错误E → G与成功值A → B源码中实现为(s) pipe(fea(s), RTE.bimap(f, ([a, s]) [g(a), s]))注意它要同步解开[A, S]元组再重新打包src/StateReaderTaskEither.tsmapLeft(f)——只映射错误as(a)——把成功值替换为常量av2.16.0asUnit()——把成功值替换为voidflap(a)——把容器里的函数应用到常量a上F(a: A) B→FB。bimap与mapLeft属于Bifunctor能力其测试可在 test/StateReaderTaskEither.ts 找到。顺序组合flatMap 全家桶与 flattensequencing是本模块组合能力的核心也是 fp-ts v2.16.0 起推荐的“扁平化”命名函数作用引入版本flatMap(f)用前一步结果A生成下一步StateReaderTaskEither环境/错误取并集R1 R2、E1 \| E2v2.14.0flatMapEither(f)后续步骤是纯Either计算v2.16.0flatMapIO(f)后续步骤是同步副作用IOv2.16.0flatMapIOEither(f)后续步骤是IOEitherv2.16.0flatMapOption(f, onNone)后续步骤是OptionNone时用onNone(a)生成错误v2.16.0flatMapReader(f)后续步骤是Reader环境取交集R1 R2v2.16.0flatMapReaderTaskEither(f)后续步骤是ReaderTaskEitherv2.16.0flatMapState(f)后续步骤是State复用同一状态Sv2.16.0flatMapTask(f)后续步骤是Taskv2.16.0flatMapTaskEither(f)后续步骤是TaskEitherv2.16.0flatten(mma)展平嵌套SRTESRTEA→SRTEA等价于flatMap(identity)v2.0.0flattenWflatten的宽松版允许内外环境/错误类型不同v2.11.0flatMap本体在源码中就是dual(2, ST.flatMap(RTE.Monad))src/StateReaderTaskEither.ts即“用RTE.Monad作为底层 monad 的 StateT 变换”。而flatMapTaskEither等则是在flatMap之上组合fromTaskEitherK之类的提升函数实现的src/StateReaderTaskEither.ts。典型异步 状态流水线示例import * as SRTE from fp-ts/StateReaderTaskEither import { pipe } from fp-ts/function import * as T from fp-ts/Task interface Counter { readonly n: number } const run: SRTE.StateReaderTaskEitherCounter, unknown, Error, string pipe( SRTE.getCounter(), SRTE.flatMap((c) pipe( SRTE.fromTask(T.of(value ${c.n})), // 模拟异步计算 SRTE.flatMap((msg) SRTE.modify((s: Counter) ({ n: s.n 1 })).pipe( // 注意此处用 pipe 链继续 )) )) )副作用组合tap / tapEither / tapIO / tapReader / tapTaskcombinators一节的tap*家族v2.15.0 / v2.16.0 引入用于“顺序执行但保留第一个结果”即做副作用日志、缓存、校验而不改变主链路的值tap(f)——后续是同类型的StateReaderTaskEithertapEither(f)——后续是Either失败会中断主链路tapIO(f)——后续是IO同步副作用如打印日志tapReader(f)——后续是Reader读取环境做副操作环境并入R1 R2tapTask(f)——后续是Task异步副作用。源码中它们分别由chainable.tap(Chain)、tapEither_(FromEither, Chain)、tapIO_(FromIO, Chain)、tapReader_(FromReader, Chain)、tapTask_(FromTask, Chain)构造src/StateReaderTaskEither.ts是标准的“Chain 对应 FromX 实例”组合。日志场景示例测试见 test/StateReaderTaskEither.tsimport { pipe } from fp-ts/function import * as SRTE from fp-ts/StateReaderTaskEither import * as IO from fp-ts/IO const log (msg: string): IO.IOvoid () console.log(msg) const program pipe( SRTE.right(hello), SRTE.tapIO((s) log(computed: ${s})), // 打印但不改变结果 SRTE.map((s) s.toUpperCase()) // 结果仍是 hello 基础上变换 )do notationbind / bindTo / let / apS 及宽松变体utils与do notation两节提供了 fp-ts 的“do 记法”让多步计算像命令式代码一样可读bindTo(name)——把当前结果挂到对象字段上SRTEA→SRTE{ name: A }bind(name, f)——用前一步结果f生成下一步并挂字段等价于flatMap 对象扩展bindW(name, f)——bind的宽松版环境/错误类型取并集let(name, f)——用纯函数f派生新字段不涉及效果v2.13.0apS(name, fb)——并行组合把fb的结果挂到fa的对象上apSW(name, fb)——apS的宽松版v2.8.0。import * as SRTE from fp-ts/StateReaderTaskEither import { pipe } from fp-ts/function const program pipe( SRTE.right(2), SRTE.bindTo(base), SRTE.bind(doubled, ({ base }) SRTE.right(base * 2)), SRTE.let(tripled, ({ base }) base * 3), // 纯计算用 let SRTE.apS(extra, SRTE.right(10)) // 与外部效果并行组合 ) // 结果{ base: 2, doubled: 4, tripled: 6, extra: 10 }错误处理alt / altW / throwError / filterOrElse / fromPredicateerror handling与filtering两节alt(that)——失败时用兜底方案that重试属于Alt类型类“类 Semigroup 的 * - * 操作”altW是宽松版允许兜底方案拥有不同的环境/错误/结果类型并取并集throwError(e)——直接抛出一个错误等价于left源码throwError left见 src/StateReaderTaskEither.tsfilterOrElse(refinement|predicate, onFalse)——结果不满足谓词时用onFalse(a)生成错误filterOrElseW是宽松版错误类型取并集fromPredicate(refinement|predicate, onFalse)——从一个普通值构造满足谓词进Right否则用onFalse进Leftlifting 节。alt在Alt实例中的底层实现会先展开状态再委托RTE.altsrc/StateReaderTaskEither.ts测试见 test/StateReaderTaskEither.ts。import * as SRTE from fp-ts/StateReaderTaskEither // 校验失败时回退到默认值 const validated pipe( SRTE.right(42), SRTE.filterOrElse((n) n 100, () too small), SRTE.alt(() SRTE.right(0)) // 失败则回退为 0 )提升liftingfromEitherK / fromTaskK / fromOptionK 等lifting一节提供“把一个返回X的普通函数提升为返回SRTE的函数”主要用于配合flatMap/chain使用避免手动包一层提升器原函数返回类型fromEitherK(f)EitherE, BfromIOK(f)IOBfromIOEitherK(f)IOEitherE, BfromOptionK(onNone)(f)OptionBNone兜底错误fromReaderK(f)ReaderR, BfromReaderTaskEitherK(f)ReaderTaskEitherR, E, BfromStateK(f)StateS, BfromTaskK(f)TaskBfromTaskEitherK(f)TaskEitherE, B例如fromTaskK的签名会把(...a: A) TaskB变成(...a: A) StateReaderTaskEitherS, R, E, B其中R unknown、E never为默认。这些函数在源码里多数是fromXxx_(FromXxx)的一行委托见 src/StateReaderTaskEither.ts。遍历sequenceArray / traverseArray / traverseArrayWithIndextraversing一节v2.9.0提供数组遍历能力等价于ReadonlyArray#traverse(Applicative)sequenceArray(arr)——把SRTE数组翻转为单个SRTEreadonly A[]traverseArray(f)——对数组每个元素应用f产生SRTE再聚合traverseArrayWithIndex(f)——同上但回调携带索引traverseReadonlyArrayWithIndex(f)——只读数组版本v2.11.0traverseReadonlyNonEmptyArrayWithIndex(f)——非空只读数组版本结果保持非空v2.11.0。import * as SRTE from fp-ts/StateReaderTaskEither // 并发地把每个 id 转成一次查询最后得到结果数组 const results SRTE.sequenceArray([ SRTE.right(1), SRTE.right(2), SRTE.right(3) ]) // StateReaderTaskEitherS, R, E, readonly number[]类型类实例instances与 URIinstances一节列出了该模块提供的全部标准实例可用于泛型编程编写不绑定具体 monad 的代码实例类型类引入版本Functor/Pointed/Apply/Applicative/Chain/Monad函数式核心链v2.7.0 ~ v2.10.0Bifunctor/Alt双函子 / 可选回退v2.7.0FromEither/FromIO/FromTask/MonadIO/MonadTask/MonadThrow各能力注入v2.10.0FromReader/FromState环境 / 状态注入v2.11.0对应URI常量与类型别名export const URI StateReaderTaskEither // v2.0.0 export type URI typeof URIURI通过模块扩充注册进HKT的类型族URItoKind4src/StateReaderTaskEither.ts使该类型可用于所有需要URIS4/HKT4的泛型 API。运行与执行evaluate / execute / run 与 zone of deathutils一节的evaluate/execute是运行入口把SRTE收敛回ReaderTaskEitherevaluate(s)——用初始状态s运行丢弃最终状态只保留结果ASRTES,R,E,A→RTER,E,Aexecute(s)——用初始状态s运行丢弃结果只保留最终状态S→RTER,E,S。结合测试 test/StateReaderTaskEither.ts最终跑起来的完整形态是“先给状态再给环境最后执行 Task 并 await”const result await pipe( SRTE.right(aaa), SRTE.map((s) s.length), SRTE.evaluate(initialState) // 丢弃最终状态得到 RTE )(env)() // 喂环境 → 执行 Task → PromiseEitherE, Azone of death“死亡地带”一节列出了已废弃、仅供迁移参考的旧 API废弃 API替代方案说明evalState(ma, s)evaluate(s)丢弃最终状态execState(ma, s)execute(s)丢弃结果run(ma, s, r)手动ma(s)(r)()旧式三段式执行stateReaderTaskEither按需传小实例如SRTE.Functor大而全的旧实例stateReaderTaskEitherSeq按需传小实例顺序版旧实例文档对旧实例的迁移建议非常明确函数需要什么类型类就传什么小实例例如需要Functor就传SRTE.Functor而不是SRTE.stateReaderTaskEither。legacy 命名chain / chainFirst / chainXxxK 对照表legacy一节是 v2.16.0 之前的老命名全部以“Alias of ...”形式指向新 API迁移时直接替换即可legacy旧新 APIchain(f)flatMap(f)chainW(f)flatMap(f)宽松版语义chainFirst(f)tap(f)chainFirstW(f)tap(f)宽松chainEitherK(f)/chainEitherKW(f)flatMapEither(f)chainFirstEitherK(f)/chainFirstEitherKW(f)tapEither(f)chainIOK(f)/chainFirstIOK(f)flatMapIO(f)/tapIO(f)chainIOEitherK(f)/chainIOEitherKW(f)flatMapIOEither(f)chainTaskK(f)/chainFirstTaskK(f)flatMapTask(f)/tapTask(f)chainTaskEitherK(f)/chainTaskEitherKW(f)flatMapTaskEither(f)chainReaderK(f)/chainReaderKW(f)flatMapReader(f)chainFirstReaderK(f)/chainFirstReaderKW(f)tapReader(f)chainReaderTaskEitherK(f)/chainReaderTaskEitherKW(f)flatMapReaderTaskEither(f)chainStateK(f)flatMapState(f)chainOptionK(onNone)(f)/chainOptionKW(onNone)(f)flatMapOption(f, onNone)注意参数顺序变化注意chainOptionK/chainOptionKW的文档明确标注 “UseflatMapOption”且参数从柯里化的onNone先行调整为flatMap(f, onNone)双参数迁移时需留意。源码级实现速览一条从 StateT 到 RTE 的委托链通读 src/StateReaderTaskEither.ts 可以提炼出三条实现主线状态层所有状态相关操作get/put/modify/gets/of/fromState来自FromState实例或ST.*src/StateT.ts的变换器函数效果层ReaderTaskEither提供异步、环境、错误三大能力SRTE通过ST.fromF(RTE.Functor)把任意RTE值嵌入状态层组合层flatMap/tap/filterOrElse等通过“FromX实例 Chain实例”组合而成这正是 fp-ts v2.10 推荐的“小实例组合”架构也解释了为什么stateReaderTaskEither大实例被废弃。例如chainStateK chainStateK_(FromState, Chain)src/StateReaderTaskEither.ts、filterOrElse filterOrElse_(FromEither, Chain)src/StateReaderTaskEither.ts这种模式贯穿整个模块具有高度一致性。小结与选型建议什么时候用StateReaderTaskEither业务同时需要“可读写状态如分页游标、会话计数器 只读配置环境DI 异步 IO 显式错误类型”时它可以把四类横切关注点收敛到一个纯函数类型里配合pipe写出类型安全的流水线什么时候不必用它如果不需要状态直接用ReaderTaskEither不需要环境用StateTaskEither思路的StateTaskEither组合避免为了“全能”而引入多余复杂度命名约定速记W后缀 类型加宽并集/交集合并、K后缀 函数提升Kleisli 风格、*K的chain*前缀 legacy、flatMap*/tap* 新推荐命名迁移路径从旧实例stateReaderTaskEither迁移到小实例从chain*迁移到flatMap*/tap*从evalState/execState迁移到evaluate/execute详见本文 zone of death 与 legacy 对照表。本文全部 API 签名、引入版本与说明均取自 docs/modules/StateReaderTaskEither.ts.md实现细节可在 src/StateReaderTaskEither.ts、src/StateT.ts 中核对行为断言可参考 test/StateReaderTaskEither.ts 中的 651 行测试用例。赞分享开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载相关推荐DeepSeek Engineer函数调用架构详解如何实现智能文件操作自动化DeepSeek Engineer函数调用架构详解如何实现智能文件操作自动化 DeepSeek Engineer是一款强大的编码助手应用它集成了DeepSehot-lib-reloader-rs快速入门5分钟搭建你的第一个热重载项目hot lib reloader rs快速入门5分钟搭建你的第一个热重载项目 hot lib reloader rs是一个为Rust开发者打造的终极热重载工具开发工具Newsroom数据集上的RLSeq2Seq应用预训练到强化学习的完整流程指南Newsroom数据集上的RLSeq2Seq应用预训练到强化学习的完整流程指南 想要构建一个能够自动生成高质量新闻摘要的AI系统吗RLSeq2Seq项目为你开发工具上一篇鲁班H5快捷键与效率工具提升开发速度的终极清单下一篇突破S3性能瓶颈s3fs-fuse内存管理优化实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价