fp-ts 中的 Traced 余单子(Comonad):基于 `P => A` 函数的环境读取与增量构建

发布时间:2026/9/23 13:15:11
fp-ts 中的 Traced 余单子(Comonad):基于 `P => A` 函数的环境读取与增量构建 开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载导读Traced是 fp-ts 中一种以纯函数读取环境为形态的类型构造器TracedP, A本质上就是(p: P) A它把从某个可叠加的上下文P中读出值A封装成可组合的一等公民。本文以 Traced.ts 模块文档 为核心结合 Traced 源码 与 Traced 测试用例 中的ProjectBuilder实例完整讲解Traced的模型、Functor/Comonad实例以及censor、listen、listens、tracks四个工具函数的实现原理与实战用法。读完本文你将能够用Traced实现增量式、可组合的构建器如逐层叠加配置的工程脚手架生成器并理解余单子extract/extend在其中的作用。一、Traced是什么把读取环境变成类型1.1 模型定义在 fp-ts 中Traced的模型极其简单src/Traced.ts#L17-L19 中只有一行export interface TracedP, A { (p: P): A }也就是说一个TracedP, A就是一个接受P返回A的普通函数。类型参数PPosition位置或环境类型是读取的输入A读取到的值类型。从源码结构看Traced属于二元类型构造器两个类型参数因此在 src/Traced.ts#L104-L108 中通过URItoKind2注册进 fp-ts 的高阶类型HKT体系declare module ./HKT { interface URItoKind2E, A { readonly [URI]: TracedE, A } }配合 src/HKT.ts 中的URItoKind2Traced才能参与Functor2、Comonad2C等带 kind 的泛型实例构造。1.2 为什么要用函数做数据类型Traced的妙处在于函数本身就是一种数据结构。一个TracedP, A尚未被消费时它只是一个闭包只有当调用它并传入具体的P时值才会被计算出来。这带来两个直接优势延迟求值构建复杂的Traced链并不会立即执行任何计算可组合的上下文叠加借助MonoidP多个Traced的环境可以像覆盖配置一样逐层合并。模块文档在 docs/modules/Traced.ts.md 中将Traced归于model分类并标注 Added in v2.0.0说明它是 fp-ts 2.x 的基石抽象之一。二、实例Functor与getComonad2.1Functor对读取结果做映射模块文档中的Functor实例签名export declare const Functor: Functor2Traced对应的实现位于 src/Traced.ts#L114-L117export const Functor: Functor2URI { URI, map: _map }map的行为非常直观——先读取再变换src/Traced.ts#L90export const map: A, B(f: (a: A) B) E(fa: TracedE, A) TracedE, B (f) (fa) (p) f(fa(p))即map(f)(wa) (p) f(wa(p))它不改变环境P只对读取到的A做变换。测试 test/Traced.ts#L55-L58 验证了这一点const wa buildProject(myproject) U.deepStrictEqual(pipe(wa, _.map(getProjectName))(M.empty), myproject)Functor实例需要满足两条定律见 src/Functor.ts#L7-L10同一律map(fa, a a) - fa复合律map(fa, bc ∘ ab) - map(map(fa, ab), bc)由于Traced的map本质是函数复合这两条定律天然成立。2.2getComonadTraced的余单子结构模块文档的核心签名export declare function getComonadP(monoid: MonoidP): Comonad2CURI, P它依赖一个MonoidP才能为TracedP, _构造Comonad2C实例。实现见 src/Traced.ts#L62-L78export function getComonadP(monoid: MonoidP): Comonad2CURI, P { function extendA, B(wa: TracedP, A, f: (wa: TracedP, A) B): TracedP, B { return (p1) f((p2) wa(monoid.concat(p1, p2))) } function extractA(wa: TracedP, A): A { return wa(monoid.empty) } return { URI, _E: undefined as any, map: _map, extend, extract } }这里有两个关键操作extract把TracedP, A退化为A。做法是以monoid.empty幺元作为默认位置调用函数即wa(monoid.empty)。这对应 src/Comonad.ts#L15-L17 中Comonad接口对extract的定义extract: A(wa: HKTW, A) A。extend给定wa: TracedP, A和f: (wa: TracedP, A) B返回一个新的TracedP, B。它的实现是在新位置p1上把f应用到以p1为基准、用monoid.concat(p1, p2)偏移出的新读取函数上。extend继承自 src/Extend.ts#L39-L41 的Extend2C接口并向上提供Functor2C的map。从源码结构看MonoidP的empty和concat是Traced余单子语义的基石empty定义了起始位置concat定义了从当前位置偏移到相邻位置的方式。2.3 类型变量说明getComonadP返回Comonad2CURI, P其中C表示部分应用CurriedP已被固定剩下的A作为自由类型参数。这在 src/Comonad.ts#L39-L41 中有完整定义export interface Comonad2CW extends URIS2, E extends Extend2CW, E { readonly extract: A(wa: Kind2W, E, A) A }三、mapping 一族map与flap3.1map映射模块文档对map的说明是标准的 Functor 语义mapcan be used to turn functions(a: A) Binto functions(fa: FA) FBwhose argument and return types use the type constructorFto represent some computational context.签名Added in v2.0.0export declare const map: A, B(f: (a: A) B) E(fa: TracedE, A) TracedE, B注意这里的E就是模型里的P位置。map是柯里化的可直接配合pipe使用例如 test/Traced.ts#L56-L57 中pipe(wa, _.map(getProjectName))。3.2flap翻转应用flapAdded in v2.10.0是一个函数值翻转工具export declare const flap: A(a: A) E, B(fab: TracedE, (a: A) B) TracedE, B其实现只是基于Functor实例的派生src/Traced.ts#L123export const flap /*#__PURE__*/ flap_(Functor)而flap_的通用实现见 src/Functor.ts#L153-L155return (a) (fab) F.map(fab, (f) f(a))即先准备好一个值a: A再拿到一个环境读取函数TracedE, A B最后得到TracedE, B。语义上等价于把a应用到环境中的函数上。四、utils位置Position操作四件套这四个工具函数是本模块最具实用价值的部分全部 Added in v2.0.0实现在 src/Traced.ts#L27-L56。4.1listen取出当前位置Get the current positionexport declare function listenP, A(wa: TracedP, A): TracedP, [A, P]实现export function listenP, A(wa: TracedP, A): TracedP, [A, P] { return (e) [wa(e), e] }它把读取结果与当前位置打包成元组[A, P]相当于审计不仅知道读到了什么还知道是从哪个位置读的。测试 test/Traced.ts#L93-L107 展示了C.extract(_.listen(buildProject(myproject)))的结果是[Project, Settings]二元组。4.2listens取出依赖位置的派生值Get a value which depends on the current positionexport declare function listensP, B(f: (p: P) B): A(wa: TracedP, A) TracedP, [A, B]实现export function listensP, B(f: (p: P) B): A(wa: TracedP, A) TracedP, [A, B] { return (wa) (e) [wa(e), f(e)] }与listen的区别在于它不直接返回原始位置P而是先用f: (p: P) B对位置做一次变换再与读取结果打包。例如 test/Traced.ts#L109-L127 中_.listens((settings) settings.settingsTravis)取出的辅助值就是当前是否开启 Travis这个布尔派生量。4.3censor改写当前位置Apply a function to the current positionexport declare function censorP(f: (p: P) P): A(wa: TracedP, A) TracedP, A实现export function censorP(f: (p: P) P): A(wa: TracedP, A) TracedP, A { return (wa) (e) wa(f(e)) }censor不改变结果类型A而是在读取前用f对位置做改写——这正是覆盖配置的原语。测试 test/Traced.ts#L129-L147 中_.censor((settings) ({ ...settings, settingsHasLibrary: !settings.settingsHasLibrary }))把settingsHasLibrary取反最终C.extract得到settingsHasLibrary: true。4.4tracks按相对位置提取值Extracts a value at a relative position which depends on the current value.export declare function tracksP, A(M: MonoidP, f: (a: A) P): (wa: TracedP, A) A实现src/Traced.ts#L27-L29源码注释标注了 TODO: curry in v3即 v3 将调整柯里化顺序export function tracksP, A(M: MonoidP, f: (a: A) P): (wa: TracedP, A) A { return (wa) wa(f(wa(M.empty))) }tracks是最有意思的一个它先在默认位置M.empty上读取wa得到a再用f(a)计算出相对偏移量最后在偏移后的位置wa(f(wa(M.empty)))上取值。因此它被称为追踪——根据当前值决定下一步去哪个相对位置读取。测试 test/Traced.ts#L77-L91 给出了经典用法travisB _.tracks(M, (project) ({ ...M.empty, settingsTravis: project.projectGitHub }))即如果项目启用了 GitHub就顺带开启 Travis。将其与gitHubB通过C.extend组合后projectTravis正确变为true。五、type lambdasURI与类型注册模块文档的 type lambdas 部分包含两个条目均 Added in v2.0.0export declare const URI: Traced export type URI typeof URI对应 src/Traced.ts#L96-L102。URI是一个字面量类型标签Traced配合 src/Traced.ts#L104-L108 的模块扩展将TracedE, A注册进URItoKind2从而让Functor2、Comonad2C等带 kind 的抽象可以实例化到Traced上。这是 fp-ts 中所有高阶类型HKT参与泛型编程的标准机制。六、zone of death已废弃的traced模块文档最后一部分是 zone of death废弃区export declare const traced: Functor2Traced注释明确写着 UseFunctorinstead.改用Functor实现在 src/Traced.ts#L136export const traced: Functor2URI Functor从 src/Traced.ts#L129-L135 可以看到它带有deprecated标记。也就是说traced只是Functor的别名为 v2.0.0 的早期用户保留兼容性新代码应直接使用Functor。这也解释了模块文档中Functor实例为何标注 Added in v2.7.0traced在 v2.0.0 引入、v2.7.0 被正式实例替代。七、实战用Traced构建可组合的增量构建器测试 test/Traced.ts 提供了一个非常直观的实战模型Adapted from Comonadic builders我们把它完整展开展示Traced的完整工作流。7.1 定义环境与单子import * as B from fp-ts/boolean import { Monoid, struct } from fp-ts/Monoid import * as _ from fp-ts/Traced interface Settings { readonly settingsHasLibrary: boolean readonly settingsGitHub: boolean readonly settingsTravis: boolean } const M: MonoidSettings struct({ settingsHasLibrary: B.MonoidAny, settingsGitHub: B.MonoidAny, settingsTravis: B.MonoidAny }) const C _.getComonad(M)这里用 src/Monoid.ts#L140-L151 的struct把三个布尔Monoid合成一个结构体MonoidSettings。B.MonoidAny的empty是false、concat是逻辑或见 src/boolean.ts 中MonoidAny相关定义因此M.empty就是全默认关闭的配置。7.2 定义构建器interface Project { readonly projectName: string readonly projectHasLibrary: boolean readonly projectGitHub: boolean readonly projectTravis: boolean } interface ProjectBuilder extends _.TracedSettings, Project {} const buildProject (projectName: string): ProjectBuilder (settings) ({ projectName, projectHasLibrary: settings.settingsHasLibrary, projectGitHub: settings.settingsGitHub, projectTravis: settings.settingsTravis })ProjectBuilder就是一个TracedSettings, Project给它一份Settings它返回一个Project。7.3 用extend叠加特性const hasLibraryB (wa: ProjectBuilder): Project { const p { ...M.empty, settingsHasLibrary: true } return wa(p) } const gitHubB (wa: ProjectBuilder): Project { const p { ...M.empty, settingsGitHub: true } return wa(p) }这些特性函数接收一个ProjectBuilder并返回新的Project恰好匹配extend的f: (wa) B形状。于是可以通过C.extend逐层叠加// extract以默认配置构建 C.extract(buildProject(myproject)) // { projectName: myproject, projectHasLibrary: false, projectGitHub: false, projectTravis: false } // extend 一层开启 library C.extract(C.extend(buildProject(myproject), hasLibraryB)) // projectHasLibrary: true // 两层叠加 tracks 自动联动 const travisB _.tracks(M, (project) ({ ...M.empty, settingsTravis: project.projectGitHub })) C.extract(C.extend(C.extend(buildProject(github-travis), gitHubB), travisB)) // { projectName: github-travis, projectGitHub: true, projectTravis: true }这里展示了Traced与Comonad的组合威力每个extend层都是对环境的增量改写tracks还能根据前一层的结果自动推导下一层的位置偏移最终由extract在M.empty默认环境上一次成型。7.4 与listen/listens/censor搭配listenC.extract(_.listen(buildProject(myproject)))返回[Project, Settings]可同时拿到构建结果与所用配置listenspipe(buildProject(myproject), _.listens((s) s.settingsTravis))返回[Project, boolean]取出的辅助值是当前是否启用 Traviscensor在读取前改写配置如测试中取反settingsHasLibrary。这四个工具函数让Traced既能读也能看审计当前位置、改覆盖配置、追按相对位置跳转构成一套完整的环境操作 DSL。八、小结与适用场景API类型作用引入版本TracedP, A模型(p: P) A环境读取函数v2.0.0Functor实例对读取结果mapv2.7.0getComonad(monoid)实例构造器基于MonoidP构造Comonad2Cv2.0.0mapmapping柯里化映射v2.0.0flapmapping值翻转应用v2.10.0listenutils取出当前位置v2.0.0listensutils取出位置的派生值v2.0.0censorutils改写当前位置v2.0.0tracksutils按相对位置提取值v2.0.0traced废弃用Functor替代v2.0.0废弃适用场景包括配置驱动的增量构建如测试中的工程脚手架生成、带上下文的审计日志listen/listens、基于默认值逐层覆盖的设置系统censorextract以及任何从可叠加环境中读取派生数据的领域。需要特别说明的是getComonad强依赖MonoidP只有P具备幺元与可结合拼接运算extract读默认位置和extend位置偏移才能成立。如果你想进一步深入可以继续阅读 Traced 源码、模块文档 与配套的 测试用例并结合 Comonad 接口定义 与 Monoid 构造工具 理解其抽象层级。赞分享开发工具【免费下载链接】fp-tsFunctional programming in TypeScript项目地址https://gitcode.com/gh_mirrors/fp/fp-ts点击查看免费下载相关推荐如何快速分析GPS轨迹数据GPXSee终极免费工具完整指南如何快速分析GPS轨迹数据GPXSee终极免费工具完整指南 GPS轨迹分析是户外运动爱好者和专业地理工作者的必备技能但面对众多GPS文件格式你是否感到困惑桌面应用GIS探索 fp-ts函数式编程在 TypeScript 中的力量探索 fp ts函数式编程在 TypeScript 中的力量 是一个由 Giacomo Canti 开发的开源库它为 TypeScript 提供了一套全面的开发工具LeeGo高级组件Union Brick实现复杂界面组合的终极方案LeeGo高级组件Union Brick实现复杂界面组合的终极方案 LeeGo是一个声明式、可配置且高度可重用的Swift UI开发框架让iOS界面开发像搭上一篇终极指南如何高效优化Kubeshark大规模集群流量处理性能下一篇djLint 项目使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询