effect-smol 的 Effect Schema 变更解读:有序约束(OrderedConstraint)通用化与 BigDecimal Arbitrary 生成修复
effect-smol 的 Effect Schema 变更解读有序约束OrderedConstraint通用化与 BigDecimal Arbitrary 生成修复【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code这篇技术文章基于 effect-smol 仓库中的变更说明文档 schema-ordered-arbitrary-constraints.md深入解析 Effect Schema v4 中toArbitrary属性生成机制的一项关键重构将number、date、bigint各自独立的范围约束字段统一收敛为通用的OrderedConstraintT有序约束模型同时剖析该变更顺带修复的 BigDecimal 任意值arbitrary生成问题。读者读完本文后将能理解ctx.constraints.ordered字段的完整含义与结构、自定义toArbitrary注解的迁移路径以及 fast-check 生成器与 Schema 约束之间如何协同工作。变更背景Schema 的toArbitrary与范围约束Effect Schema 除了负责运行时校验与编解码还提供Schema.toArbitrary把任意 Schema 编译为 fast-check 的Arbitrary生成器从而支撑基于属性的测试property-based testing。为了让生成的随机数据天然满足 Schema 上挂载的过滤条件filterSchema 会把诸如 大于 18、介于 10 与 20 之间 这类校验约束以结构化元数据的形式透传给生成器而不是简单地在生成后再反复重试过滤。在旧设计中这些范围信息被塞进类型特定的约束字段里number约束上有min/max/minExcluded/maxExcludedbigint约束上有对应的 bigint 版本date又有一套自己的范围字段。问题随之而来一旦希望支持任意有序类型例如BigDecimal、Duration、DateTime乃至自定义品牌类型每个类型都要重复实现一套范围推导逻辑且约束的合并merge语义难以统一。本次变更PR #2342的核心思路是引入通用的有序约束模型OrderedConstraintT让所有有序类型的范围校验统一填充到ctx.constraints.ordered后续 v4 正式 API 中为ctx.constraint?.ordered并由同一套消费逻辑翻译为 fast-check 的整数、浮点、bigint 约束。OrderedConstraint 模型统一的四元组在 Schema.ts 中Annotations.ToArbitrary.OrderedConstraintT承载了所有范围信息其核心字段为字段含义对应校验order排序语义类型为Order.OrderT决定大/小的判定标准minimum下界包含对应exclusiveMinimum下界是否排除为true时对应maximum上界包含对应exclusiveMaximum上界是否排除为true时对应同时该约束作为GenerationConstraint的一部分挂在每个 Schema 节点上。从 Schema.ts 的接口定义可见export interface GenerationConstraint { readonly minLength?: number | undefined readonly maxLength?: number | undefined readonly patterns?: readonly [string, ...Arraystring] readonly integer?: boolean | undefined readonly noInfinity?: boolean | undefined readonly noNaN?: boolean | undefined readonly unique?: boolean | undefined readonly ordered?: OrderedConstraintany | undefined // -- 范围信息统一放这里 }关于命名的补充说明变更说明文档中写的是ctx.constraints.ordered这是 v4 早期 beta 的 API 形态在后续的 API 重命名PR #2348中context.constraints被改名为单数形式的context.constraint。因此当前源码中读取方式为ctx.constraint?.ordered见 Schema.ts。本文后续统一以语义化的ctx.constraints.ordered指代该字段实际编码时请以你所用版本的类型定义为准。产生端源码验证范围校验如何填充 ordered这一节我们以源码为证看各类范围校验过滤器是如何把信息写入ordered的。它们均位于 Schema.ts 的make*系列工厂函数中返回的Annotations.Filter通过arbitrary.constraint携带生成器所需的约束。makeIsGreaterThan严格大于// Schema.ts#L7731-L7757 export function makeIsGreaterThanT(options: { readonly order: Order.OrderT readonly annotate?: ((exclusiveMinimum: T) Annotations.Filter) | undefined readonly formatter?: FormatterT | undefined }) { const gt Order.isGreaterThan(options.order) const formatter options.formatter ?? format return (exclusiveMinimum: T, annotations?: Annotations.Filter) { return makeFilterT( (input) gt(input, exclusiveMinimum), { expected: a value greater than ${formatter(exclusiveMinimum)}, arbitrary: { constraint: { ordered: { order: options.order, minimum: exclusiveMinimum, exclusiveMinimum: true // -- 下界被标记为排除 } } }, ...options.annotate?.(exclusiveMinimum), ...annotations } ) } }makeIsBetween区间makeIsBetween是最典型的代表它同时处理四个边界语义// Schema.ts#L7869-L7916节选 return (options: { readonly minimum: T readonly maximum: T readonly exclusiveMinimum?: boolean | undefined readonly exclusiveMaximum?: boolean | undefined }, annotations?: Annotations.Filter) { const gte options.exclusiveMinimum ? greaterThan : greaterThanOrEqualTo const lte options.exclusiveMaximum ? lessThan : lessThanOrEqualTo return makeFilterT( (input) gte(input, options.minimum) lte(input, options.maximum), { expected: a value between ${formatter(options.minimum)}${options.exclusiveMinimum ? (excluded) : } and ${ formatter(options.maximum) }${options.exclusiveMaximum ? (excluded) : }, arbitrary: { constraint: { ordered: { order: deriveOptions.order, minimum: options.minimum, maximum: options.maximum, ...(options.exclusiveMinimum { exclusiveMinimum: true }), ...(options.exclusiveMaximum { exclusiveMaximum: true }) } } }, // ... } ) }变更前后的对照维度变更前变更后约束载体number/date/bigint各自的 range 字段如min、maxExcluded统一的ordered字段minimum/maximum/exclusiveMinimum/exclusiveMaximum排序来源各类型隐式内建显式携带Order.OrderT可支持类型number、date、bigint 等预置类型任何有Order实例的有序类型含 BigDecimal、Duration 等消费逻辑每类型一套推导共用toRangeConstraints一套翻译正是这种类型无关的建模让Schema.isGreaterThan(...)、Schema.isLessThan(...)、Schema.isBetween(...)等 API它们都基于上述make*工厂如Number、BigInt、BigDecimal、Duration等模块中均有对应实例首次能够以完全一致的方式驱动任意类型的生成器。消费端源码验证ordered 如何翻译为 fast-check 约束生成端负责写消费端负责读。在 toArbitrary.ts 中ordered被翻译成 fast-check 能理解的原生约束。整数toIntegerConstraints// toArbitrary.ts#L303-L310 function toIntegerConstraints(ordered: OrderedConstraint | undefined) { return toRangeConstraintsnumber( ordered, (minimum, excluded) excluded ? Math.floor(minimum) 1 : Math.ceil(minimum), (maximum, excluded) excluded ? Math.ceil(maximum) - 1 : Math.floor(maximum), integer constraints ) }对于整数排除语义exclusive通过向区间内部收缩一位实现 3变成min 4 10变成max 9。浮点数toFloatConstraints// toArbitrary.ts#L312-L329 function toFloatConstraints(constraint: Constraint | undefined, ordered: OrderedConstraint | undefined) { const out: FastCheck.FloatConstraints { ...(constraint?.noInfinity ? { noDefaultInfinity: true } : {}), ...(constraint?.noNaN ? { noNaN: true } : {}), ...(ordered?.minimum ! undefined ? { min: ordered.minimum as number } : {}), ...(ordered?.exclusiveMinimum ! undefined ? { minExcluded: ordered.exclusiveMinimum } : {}), ...(ordered?.maximum ! undefined ? { max: ordered.maximum as number } : {}), ...(ordered?.exclusiveMaximum ! undefined ? { maxExcluded: ordered.exclusiveMaximum } : {}) } // 自相矛盾的区间直接抛错 if ( out.min ! undefined out.max ! undefined (out.min out.max || (out.min out.max (out.minExcluded || out.maxExcluded))) ) { throw arbitraryError(number constraints) } return out }浮点类型保留 fast-check 的minExcluded/maxExcluded原生字段且在min max且任一端被排除时即区间[x, x]但又要严格排除某侧数学上为空集抛出明确的number constraints错误。大整数与兜底逻辑toRangeConstraints/toBigIntConstraints// toArbitrary.ts#L284-L301 function toRangeConstraintsT extends number | bigint( ordered: OrderedConstraint | undefined, min: (value: T, excluded: boolean) T, max: (value: T, excluded: boolean) T, error: string ) { const out: { min?: T; max?: T } {} if (ordered?.minimum ! undefined) { out.min min(ordered.minimum as T, ordered.exclusiveMinimum true) } if (ordered?.maximum ! undefined) { out.max max(ordered.maximum as T, ordered.exclusiveMaximum true) } if (out.min ! undefined out.max ! undefined out.min out.max) { throw arbitraryError(error) } return out }toBigIntConstraints与整数版行为一致只是边界移动单位为BigInt(1)// toArbitrary.ts#L331-L338 function toBigIntConstraints(ordered: OrderedConstraint | undefined) { return toRangeConstraintsbigint( ordered, (minimum, excluded) excluded ? minimum BigInt(1) : minimum, (maximum, excluded) excluded ? maximum - BigInt(1) : maximum, the ordered bigint constraints ) }这三套翻译逻辑共同证明了变更的核心价值无论 Schema 挂载的是哪种有序类型约束都先归一化为ordered再由对应数值种类整数/浮点/bigint的翻译函数各取所需消除了原先每类型各自维护范围字段的重复与漂移。迁移指南自定义 toArbitrary 注解的升级路径变更说明明确给出了对下游用户的唯一破坏性提示凡是自定义toArbitrary注解、且内部读取范围约束的代码都需要从类型特定的 range 字段迁移到ctx.constraints.ordered。迁移前后对照示例// 迁移前读取类型特定字段已废弃 toArbitrary: (fc, ctx) { const min ctx.constraints.number?.min const max ctx.constraints.number?.max // ... } // 迁移后读取通用 ordered 字段 toArbitrary: (fc, ctx) { const ordered ctx.constraint?.ordered const minimum ordered?.minimum // T | undefined const maximum ordered?.maximum // T | undefined const minExcluded ordered?.exclusiveMinimum true const maxExcluded ordered?.exclusiveMaximum true // 使用 (fc, ctx) 自行构造生成器再经由 filter 校验 }配合本次变更之前的一次性字段名迁移可以一并完成。参照 CHANGELOG.md 中给出的对照表ordered.min/minExcluded/max/maxExcluded→ordered.minimum/exclusiveMinimum/maximum/exclusiveMaximum读取入口从context.constraints改为context.constraint建议的迁移顺序先用ctx.constraint?.ordered替换掉对number/date/bigint旧 range 字段的所有读取若注解依赖排序语义显式传入Order实例例如BigDecimal.Order而不是假设类型自带排序迁移后运行既有 property 测试重点观察边界值恰好等于下界/上界、排除端点、空区间报错行为是否保持一致利用Schema.toArbitrary(schema, { report: true })输出生成报告校验生成分布是否仍落在期望区间内。附带的 Bug 修复BigDecimal 窄区间任意值生成变更说明提到该 PR 同时修复了 BigDecimal arbitrary 生成通过把十进制边界适配到生成的小数位scale避免窄十进制区间产生无效的 fast-check bigint 范围。这在 Schema.ts 的BigDecimal声明中有完整实现。问题本质BigDecimal由未缩放的 bigint 数值 scale 小数位二元组表示。旧实现直接拿用户给定的十进制边界如[1.2, 1.3]在默认 scalebigDecimalDefaultMaxScale 20见 Schema.ts下换算成 bigint 区间。当区间很窄例如isBetween(1.2, 1.3)时换算出的 bigint 下界与上界之差小于 1fast-check 会得到min max的非法 bigint 范围导致生成器直接崩溃。修复策略边界随 scale 动态适配新实现的核心是先确定一个可行的 scale 范围再在 scale 上做二分收缩保证每个被选中的 scale 都能构造出非空 bigint 区间// Schema.ts#L12590-L12626 function bigDecimalValueConstraintsAtScale( ordered: OrderedConstraintBigDecimal_.BigDecimal, scale: number ): FastCheck.BigIntConstraints | undefined { const constraints: FastCheck.BigIntConstraints {} if (ordered.minimum ! undefined) { constraints.min bigDecimalMinValueAtScale(ordered.minimum, scale, ordered.exclusiveMinimum true) } if (ordered.maximum ! undefined) { constraints.max bigDecimalMaxValueAtScale(ordered.maximum, scale, ordered.exclusiveMaximum true) } if (constraints.min ! undefined constraints.max ! undefined constraints.min constraints.max) { return undefined // 该 scale 下无合法区间调用方会尝试更粗的 scale } return constraints } function bigDecimalScaleConstraints( ordered: OrderedConstraintBigDecimal_.BigDecimal ): FastCheck.IntegerConstraints { const max bigDecimalMaxScale(ordered) if (bigDecimalValueConstraintsAtScale(ordered, max) undefined) { throw new globalThis.Error(bigDecimalInvalidOrderedConstraintsError) // 最细 scale 也放不下直接报错 } let min 0 let high max while (min high) { // 二分查找找到能容纳区间的最粗可行 scale const scale min Math.floor((high - min) / 2) if (bigDecimalValueConstraintsAtScale(ordered, scale) undefined) { min scale 1 } else { high scale } } return { min, max } }边界在给定 scale 下的换算由bigDecimalMinValueAtScale/bigDecimalMaxValueAtScale完成排除端点时分别floor 1/ceil - 1包含端点时分别ceil/floor见 Schema.ts。可用的 scale 上限由bigDecimalMaxScale决定取默认最大 scale20与两端边界自身 scale 的最大值若端点被排除还需再加 1保证区间内至少能容纳一个中间值见 Schema.ts。最终生成时toArbitrary先以fc.integer(bigDecimalScaleConstraints(ordered))随机选一个可行 scale再chain到该 scale 下的fc.bigInt(constraints)最后map为BigDecimal.make(value, scale)见 Schema.ts。这套二分收缩 逐 scale 适配的算法保证了像isBetween(1.2, 1.3)这样的窄区间也能稳定生成合法的 BigDecimal 样本且生成分布会自适应到恰好能容纳边界的精度不会因默认高精度而产生空集。总结本次 changeset 所描述的变更本质上是 Effect Schema v4 对 arbitrary 生成约束体系的一次去类型化收敛统一模型isGreaterThan、isLessThan、isBetween等范围校验统一写入ctx.constraints.orderedOrderedConstraintT以orderminimum/maximum/exclusiveMinimum/exclusiveMaximum表达全部边界语义统一消费toArbitrary内部以toRangeConstraints为骨架分别按整数、浮点、bigint 翻译成 fast-check 约束并对空区间、自相矛盾区间显式抛错破坏性迁移点自定义toArbitrary注解中读取范围约束的代码需从类型特定的旧字段迁移到ordered额外修复BigDecimal 生成改为按 scale 动态适配边界通过二分收缩选择可行 scale彻底解决了窄十进制区间的非法 bigint 范围问题。对于在 property-based testing 中深度使用 Effect Schema 的开发者本变更意味着今后为任意自定义有序类型编写生成器时只要类型提供了Order实例范围约束就能零成本地驱动任意值生成无需再为每种类型手写一套边界换算逻辑。相关的权威证据可进一步查阅 CHANGELOG.md、Schema.ts、toArbitrary.ts 与变更说明原文 schema-ordered-arbitrary-constraints.md。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

如何三步跑通 Kimi K2 工具调用:实操指南

如何三步跑通 Kimi K2 工具调用:实操指南

如何三步跑通 Kimi K2 工具调用:实操指南 【免费下载链接】Kimi-K2 Kimi K2 is the large language model series developed by Moonshot AI team 项目地址: https://gitcode.com/GitHub_Trending/ki/Kimi-K2 手动修一个 GitHub Issue,通常要读仓…

2026/11/12 17:40:48 阅读更多 →
React并发模式下Recoil状态管理:原理、快照与迁移实践

React并发模式下Recoil状态管理:原理、快照与迁移实践

自从React 18把并发渲染真正落地之后,很多状态管理库突然变得非常尴尬。Redux和Mobx的Issue区里一度涌进了大量和“tearing”相关的报告,维护者们连夜讨论如何在外部store的前提下保证渲染一致性。与此同时,Facebook内部孵化并开源的Recoil却…

2026/10/19 6:52:37 阅读更多 →
如何用 @midscene/test 创建 Web 测试项目并运行示例用例

如何用 @midscene/test 创建 Web 测试项目并运行示例用例

如何用 midscene/test 创建 Web 测试项目并运行示例用例 【免费下载链接】midscene GUI Agent for E2E Testing 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene midscene/test(Midscene Test)是 Midscene 提供的 AI 端到端测试框架…

2026/10/22 3:30:08 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/12 5:00:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/12 5:13:20 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/12 5:13:20 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/11 17:16:37 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/11 17:16:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/12 5:13:20 阅读更多 →

日新闻

周新闻

月新闻