
后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载io-tsRuntime type system for IO decoding/encoding在io-ts/Eq模块中提供了一套基于 fp-tsEq类型类的运行时相等性equality工具用于对解码后的数据执行结构化的、可组合的相等比较。本指南以 docs/modules/Eq.ts.md 的 API 参考为骨架结合 src/Eq.ts 源码实现与 test/Eq.ts 测试用例系统讲解该模块的原始类型、组合子combinators、实例instances与类型工具读完你可以直接用Eq组合子为对象、数组、元组、可选字段、可空值、可辨识联合等常见数据结构搭建精确的相等性判等器并理解其与Decoder、Guard、Type、TaskDecoder等其他 io-ts 模块共享的 Schemable 抽象。注意该模块自 v2.2.2 起作为**实验性experimental**功能发布部分 API 为 v2.2.3、v2.2.8、v2.2.15 加入API 处于高度变动状态可能在没有通知的情况下改变。发布实验性功能的目的是尽早获得社区反馈。Eq 类型类判等能力的最小抽象Eq是 fp-ts 中表示可判等类型的类型类仓库根目录的 Eq.md 给出了其核心接口定义export interface EqA { readonly equals: (x: A, y: A) boolean }一个合法的EqA实例必须满足三条定律自反性ReflexivityE.equals(a, a) true对称性SymmetryE.equals(a, b) E.equals(b, a)传递性Transitivity若E.equals(a, b) true且E.equals(b, c) true则E.equals(a, c) true最简单的实例是直接基于实现例如string: Eqstring { equals: (x, y) x y }。而io-ts/Eq模块的价值在于它提供了一套与 io-ts 解码器结构一一对应的组合子使你可以用与构造 codec 几乎相同的写法来构造相等性判等器从而保证解码后如何比较与解码规则天然对齐。模块 API 总览io-ts/Eq模块的导出按四类组织见 src/Eq.ts 中的category注解分类导出项primitives原始类型string、number、boolean、UnknownArray、UnknownRecordcombinators组合子nullable、struct、type已弃用、partial、record、array、tuple、intersect、sum、lazy、readonlyinstances实例Schemable、WithUnknownContainers、WithRefineutils类型工具TypeOf类型别名、URI类型别名其中Schemable、WithUnknownContainers、WithRefine三个实例是 io-ts 2.2 引入的 Schemable 抽象 在Eq类型上的实现它们使得Eq可以与Decoder、Guard、Type、TaskDecoder等模块同构地组合。从源码结构看src/Eq.ts、src/Decoder.ts、src/Guard.ts、src/Type.ts、src/TaskDecoder.ts这些模块各自导出了同名的三个实例结构完全平行。primitives内建的原始相等性五个原始Eq实例中前三个直接复用 fp-ts 提供的内建实例src/Eq.tsexport const string: Eqstring E.eqString export const number: Eqnumber E.eqNumber export const boolean: Eqboolean E.eqBoolean后两个则针对未知容器进行了专门定义其语义值得特别注意// src/Eq.ts#L59 export const UnknownArray: EqArrayunknown E.fromEquals((x, y) x.length y.length) // src/Eq.ts#L65 export const UnknownRecord: EqRecordstring, unknown E.fromEquals((x, y) { for (const k in x) { if (!(k in y)) return false } for (const k in y) { if (!(k in x)) return false } return true })UnknownArray只比较数组长度UnknownRecord只比较键集合是否一致不比较值。这一点在 test/Eq.ts 中被明确验证assert.deepStrictEqual(eq.equals([a], [b]), true) // UnknownArray仅长度参与比较 assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 2 }), true) // UnknownRecord仅键集合参与比较 assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 1, b: true }), false)这种宽松设计是刻意的当值类型未知时无法对值做有意义的比较因此只比较可可靠观察到的结构信息长度 / 键集合可作为更深层比较的退路。combinators从简单类型组合出复杂判等器nullable可空值判等nullableA(or: EqA): Eqnull | A为null | A类型构造判等器其实现逻辑为只要两侧有一侧是null就退化为判断即null只等于null否则委托给底层Eqsrc/Eq.tsexport function nullableA(or: EqA): Eqnull | A { return { equals: (x, y) (x null || y null ? x y : or.equals(x, y)) } }struct 与已弃用的 type对象判等structA(eqs: { [K in keyof A]: EqA[K] }): Eq{ [K in keyof A]: A[K] }直接委托给 fp-ts 的E.getStructEq对每个键的取值逐项比较src/Eq.ts。type是struct的旧名已被标记deprecated文档明确标注Usestructinsteadsrc/Eq.tsexport const struct: A(eqs: { [K in keyof A]: EqA[K] }) Eq{ [K in keyof A]: A[K] } E.getStructEq /** deprecated */ export const type struct典型用法沿用 Eq.md 的示例import * as E from io-ts/Eq const Person E.type({ name: E.string, age: E.number }) Person.equals({ name: a, age: 0 }, { name: a, age: 0 }) // true Person.equals({ name: a, age: 0 }, { name: , age: 0 }) // false Person.equals({ name: a, age: 0 }, { name: a, age: 1 }) // falsepartial可选字段判等partialA(properties: { [K in keyof A]: EqA[K] }): EqPartial{ [K in keyof A]: A[K] }处理所有键都可选的场景。其实现逐键比较并对undefined做了专门处理键缺失undefined与键存在但值为undefined被视为相等否则才委托给底层Eqsrc/Eq.tsexport function partialA(properties: { [K in keyof A]: EqA[K] }): EqPartial{ [K in keyof A]: A[K] } { return { equals: (x, y) { for (const k in properties) { const xk x[k] const yk y[k] if (!(xk undefined || yk undefined ? xk yk : properties[k].equals(xk as any, yk as any))) { return false } } return true } } }test/Eq.ts 验证了该语义const eq E.partial({ a: E.number }) eq.equals({ a: undefined }, { a: undefined }) // true eq.equals({}, { a: undefined }) // true键缺失等价于值为 undefined eq.equals({ a: 1 }, {}) // falserecord 与 array同质容器判等recordA(codomain: EqA): EqRecordstring, A与arrayA(eq: EqA): EqArrayA分别委托给 fp-ts 的R.getEq与A.getEqsrc/Eq.ts语义为键集合必须一致且每个位置/每个键的值分别相等。tuple元组判等tupleA extends readonly unknown[](...components: { [K in keyof A]: EqA[K] }): EqA委托给 fp-ts 的E.getTupleEq按位置逐项比较src/Eq.tsconst eq E.tuple(E.string, E.number) eq.equals([a, 1], [a, 1]) // true eq.equals([a, 1], [b, 1]) // false eq.equals([a, 1], [a, 2]) // falseintersect交集判等intersect采用柯里化形式intersectB(right: EqB) A(left: EqA) EqA B实现为两侧判等器都返回 true 才算相等src/Eq.tsexport const intersect B(right: EqB) A(left: EqA): EqA B ({ equals: (x, y) left.equals(x, y) right.equals(x, y) })配合pipe使用可以对对象交集做组合判等test/Eq.tsconst eq pipe(E.struct({ a: E.string }), E.intersect(E.struct({ b: E.number }))) eq.equals({ a: a, b: 1 }, { a: a, b: 1 }) // true eq.equals({ a: a, b: 1 }, { a: c, b: 1 }) // false eq.equals({ a: a, b: 1 }, { a: a, b: 2 }) // falsesum可辨识联合判等sumT extends string(tag: T) A(members: { [K in keyof A]: EqA[K] RecordT, K }) EqA[keyof A]为带tag标签的可辨识联合discriminated union构造判等器。实现先比较两侧的tag字段值不同则直接判不等相同则委托给对应成员的判等器src/Eq.tsexport function sumT extends string(tag: T): ... { return (members: Recordstring, Eqany) ({ equals: (x: any, y: any) { const vx x[tag] const vy y[tag] if (vx ! vy) return false return members[vx].equals(x, y) } }) }test/Eq.ts 展示了典型用法用_tag作为判别字段配合literal约束每个分支的标签值const sum E.sum(_tag) const eq sum({ A: E.struct({ _tag: E.Schemable.literal(A), a: E.string }), B: E.struct({ _tag: E.Schemable.literal(B), b: E.number }) }) eq.equals({ _tag: A, a: a }, { _tag: B, b: 1 }) // false标签不同 eq.equals({ _tag: A, a: a }, { _tag: A, a: b }) // falselazy递归结构判等lazyA(f: () EqA): EqA用于打破递归数据结构的自引用。实现借助 Schemable.ts 导出的memoize缓存工厂函数的结果确保递归求值只执行一次export function lazyA(f: () EqA): EqA { const get memoizevoid, EqA(f) return { equals: (x, y) get().equals(x, y) } }递归树的判等示例test/Eq.tsinterface A { a: number b: ArrayA } const eq: EqA E.Schemable.lazy(A, () E.struct({ a: E.number, b: E.array(eq) }) ) eq.equals({ a: 1, b: [{ a: 2, b: [] }] }, { a: 1, b: [{ a: 2, b: [] }] }) // true eq.equals({ a: 1, b: [{ a: 2, b: [] }] }, { a: 1, b: [{ a: 3, b: [] }] }) // falsereadonly只读包装判等readonlyA(eq: EqA): EqReadonlyA自 v2.2.15 加入实现就是恒等函数identitysrc/Eq.tsReadonlyA只是编译期的类型约束运行时结构相同因此直接透传即可。instancesSchemable 抽象在 Eq 上的落地Schemable.ts 定义了一个统一的、可用于构造多种运行时产物的抽象接口含literal、string、number、boolean、nullable、type、struct、partial、record、array、tuple、intersect、sum、lazy、readonly等方法。io-ts/Eq中的三个实例就是该抽象针对Eq类型的具体实现src/Eq.tsexport const Schemable: Schemable1Eq { URI: E.URI, literal: () E.eqStrict, string, number, boolean, nullable, type, struct, partial, record, array, tuple, intersect, sum, lazy: (_, f) lazy(f), readonly } export const WithUnknownContainers: WithUnknownContainers1Eq { UnknownArray, UnknownRecord } export const WithRefine: WithRefine1Eq { refine: () (from) from }三个实例各有要点Schemable将上述全部组合子统一收编其中literal字面量判等实现为 fp-ts 的E.eqStrict即基于的严格相等。这意味着你可以编写与 codec 定义完全平行的代码——用同一套struct/array/sum/lazy方法名为Eq、Decoder、Guard、Type、TaskDecoder分别产出不同性质的运行时产物。这也是 io-ts 2.2 中一个 schema 描述、多套解释器设计的核心。WithUnknownContainers提供未知容器UnknownArray、UnknownRecord的实例语义与前述 primitives 相同只比较长度 / 键集合。WithRefinerefine: () (from) from是恒等变换。从源码结构看原因在于精化refinement只改变类型层面的约束如string→ 非空字符串并不改变运行时结构所以相等性判等器无需任何变化——直接透传原判等器即可。test/Eq.ts 验证了这一点对string判等器做WithRefine.refine精化到NonEmptyString品牌类型后判等行为不受影响。utils类型层面的工具export type TypeOfE E extends Eqinfer A ? A : never // 提取 Eq 承载的类型 A export type URI E.URI // Eq用于类型级编程TypeOf条件类型从EqA实例中推断出被比较的类型A与 io-ts 主模块的TypeOf工具见 docs/modules/index.ts.md思路一致便于在泛型中提取判等器的目标类型。URI值为 fp-ts 的E.URI即字符串Eq用于类型级编程与 HKT 相关的泛型推导Schemable1Eq的类型参数正是它。实战构建一套完整的 Person 判等方案将上述 API 组合起来可以得到与 io-ts codec 定义几乎逐行对应的判等器集合综合 Eq.md 示例与 src/Eq.ts 组合子import * as E from io-ts/Eq // 原始类型 const name E.string const age E.number // 可选字段 可空字段 const nickname E.nullable(E.string) const bio E.partial({ city: E.string, tags: E.array(E.string) }) // 对象 const Address E.struct({ street: E.string, zip: E.number }) // 可辨识联合 const Contact E.sum(kind)({ email: E.struct({ kind: E.Schemable.literal(email), address: E.string }), phone: E.struct({ kind: E.Schemable.literal(phone), number: E.string }) }) // 递归结构 interface Node { value: number children: ArrayNode } const NodeEq: E.EqNode E.lazy(() E.struct({ value: E.number, children: E.array(NodeEq) })) // 最终对象 const PersonEq E.struct({ name, age, nickname, bio, address: Address, contacts: E.array(Contact) })此时PersonEq.equals(personA, personB)会递归地、按结构精确比较两个对象并遵循自反、对称、传递三条定律。与 io-ts 其他模块的关系Eq模块本身不参与 IO 解码/编码但它与 io-ts 的运行时验证体系密切相关当你用Decoder/Type校验并解码数据后往往还需要判断解码出的两个值是否相等例如做缓存命中、变更检测、测试断言。io-ts/Eq提供的组合子与解码器的结构一一对应让你能以几乎相同的 schema 写法获得一致的相等性语义。同一套 Schemable 抽象也被 Decoder.ts、Guard.ts、Type.ts、TaskDecoder.ts 复用对应文档见 docs/modules/Decoder.ts.md、docs/modules/Guard.ts.md、docs/modules/Type.ts.md、docs/modules/TaskDecoder.ts.md。版本与实验性状态说明模块自v2.2.2加入primitives、nullable、type已弃用、partial、record、array、tuple、intersect、sum、lazy、TypeOf。v2.2.3加入URI类型别名。v2.2.8加入Schemable、WithUnknownContainers、WithRefine三个实例。v2.2.15加入struct并弃用type与readonly。整个模块标记为experimental功能发布目的是尽早获取社区反馈对应 GitHub 上label:v2.2的跟踪 issue处于高度变动状态使用时有 API 变更风险。当前仓库版本为 io-ts 2.2.21见 package.jsonEq模块的完整可运行测试见 test/Eq.ts类型级接口定义可查阅 Schemable.ts 与 fp-ts 的Eq模块peerDependencies: fp-ts ^2.5.0。赞分享后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载相关推荐Cursor 集成 Xero 财务数据xero 插件本地 MCP 服务器接入与 Custom Connection 认证实践Cursor 集成 Xero 财务数据xero 插件本地 MCP 服务器接入与 Custom Connection 认证实践 本指南围绕开源仓库 GitHub后端fp-ts Eq 类型类完全指南可判定相等性的建模、组合与最佳实践fp ts Eq 类型类完全指南可判定相等性的建模、组合与最佳实践 Eq 是 fp ts 中表示 可判定相等性decidable equality 的核心开发工具OpenCore Legacy Patcher 完整新手指南如何让旧 Mac 免费安装最新 macOSOpenCore Legacy Patcher 完整新手指南如何让旧 Mac 免费安装最新 macOS 如果你的 Mac 是 Intel 芯片、多年停在老系统后端上一篇MockServer Java客户端完全指南与服务器无缝交互的最佳实践下一篇终极Chrome编码设置指南5分钟解决网页乱码问题 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考