Effect 4.xTaggedUnion.match类型修复借助 Unify 协议统一模式匹配分支返回类型【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect本文围绕 Effect 仓库中一条 patch 级变更记录.changeset/pre/fix-tagged-union-match-unify.md展开它修复了Schema.TaggedUnion/Schema.toTaggedUnion提供的match方法在类型层面的返回值推断问题让每个分支可以返回互不相同的 Effect 类型并通过 Effect 的Unify协议在编译期自动合并为可继续链式调用的统一类型。读完本文你将理解该修复的动机、Unify类型协议的底层原理、match/matchOrElse的类型签名变化以及它在源码与测试中的具体落点。一、这条 changeset 说了什么changeset 是 Effect 仓库基于 Changesets 工具记录版本变更的载体位于.changeset/pre/目录下。该文件内容如下--- effect: patch --- Fix TaggedUnion.match to use Unify for return types, allowing branches to return distinct Effect types that are properly merged.逐项解读effect: patch变更影响effect主包级别为 patch补丁。这意味着它是向后兼容的缺陷修复不会破坏现有 API将在下一个 patch 版本中随主包一起发布。修复对象TaggedUnion.match即Schema.TaggedUnion(...)与Schema.Union([...]).pipe(Schema.toTaggedUnion(...))附带的方法之一。修复内容match的返回值类型改用Unify进行规范化使各分支可以返回不同的 Effect 类型例如错误类型不同的Effect并在类型层面被正确合并properly merged。这条记录本身只有两行正文但指向的功能——TaggedUnion.match与Unify协议——在仓库源码中有完整的实现下面结合源码逐层展开。二、背景TaggedUnion与toTaggedUnion是什么Schema.TaggedUnion是 Effect Schema 模块中用于构建带判别字段discriminant的联合结构的构造器。典型用法出自 Schema.ts 的 JSDoc 示例import { Schema } from effect const Shape Schema.TaggedUnion({ Circle: { radius: Schema.Number }, Rectangle: { width: Schema.Number, height: Schema.Number } }) // Shape 携带 cases、guards、isAnyOf、match、matchOrElse 等实用方法如果已有现成的Schema.Union也可以用Schema.toTaggedUnion(_tag)为其增强同样的方法集toTaggedUnion 的 JSDocimport { Schema } from effect const A Schema.TaggedStruct(A, { value: Schema.Number }) const B Schema.TaggedStruct(B, { name: Schema.String }) const MyUnion Schema.Union([A, B]).pipe(Schema.toTaggedUnion(_tag)) const result MyUnion.match({ _tag: A, value: 1 }, { A: (a) number: ${a.value}, B: (b) name: ${b.name} }) // result number: 1从 toTaggedUnion 的运行时实现可以看到match的运行时逻辑非常直接读取value[tag]判别值用Object.hasOwn找到对应的处理器函数并调用同时支持match(value, cases)与柯里化match(cases)(value)两种调用形式matchOrElse则在找不到处理器时回退到orElse函数。因此这次修复是纯类型层面的改动不涉及运行时行为变化。三、修复核心match的返回类型改用Unify3.1 修复前的类型签名问题所在在修复之前TaggedUnionUtils中match的返回类型是分支返回类型的裸联合。考虑如下场景import { Schema, Effect } from effect const Event Schema.TaggedUnion({ Success: { data: Schema.String }, Failure: { error: Schema.String } }) const program Event.match({ Success: ({ data }) Effect.succeed(data), // Effectnever, never, string Failure: ({ error }) Effect.fail(error) // Effectnever, string, never })若返回类型不做规范化program会被推断为Effectnever, never, string | Effectnever, string, never。这种联合的 Effect在随后调用.pipe(...)等链式 API 时会非常难用TypeScript 只能对联合的公共成员调用方法且R/E类型参数无法自然地归并这正是 changeset 所说的无法被正确合并的痛点。3.2 修复后的类型签名源码证据修复后的定义位于 packages/effect/src/Schema.ts 的TaggedUnionUtils类型中match的两个重载均以UnifyR收尾readonly match: { Cases extends { [M in Flattened[number] as M[Type][Tag]]: (value: M[Type]) any } ( value: Members[number][Type], cases: Cases ): Cases[keyof Cases] extends (value: any) infer R ? UnifyR : never Cases extends { [M in Flattened[number] as M[Type][Tag]]: (value: M[Type]) any } ( cases: Cases ): (value: Members[number][Type]) Cases[keyof Cases] extends (value: any) infer R ? UnifyR : never }两个重载分别对应match(value, cases)与柯里化match(cases)(value)二者都先把所有分支的返回类型收拢为联合RCases[keyof Cases] extends (value: any) infer R ? R : never再交给UnifyR规范化。这样上例中program会被推断为单个Effectnever, string, string可以直接继续链式操作。3.3matchOrElse同样被 Unify 覆盖改动不仅限于match。matchOrElse的返回类型通过MatchOrElseResult计算同样显式包裹了UnifySchema.tstype MatchCasesResultCases { [K in keyof Cases]-?: NonNullableCases[K] extends (...args: Arrayany) infer R ? R : never }[keyof Cases] type MatchOrElseResultCases, OrElse extends (...args: Arrayany) any Unify MatchCasesResultCases | ReturnTypeOrElse 可见联合的各分支返回类型 orElse 回退类型整体都被Unify规范化保证matchOrElse在分支返回不同 Effect 类型时也能推断出可用的统一类型。四、Unify协议底层原理Unify是 Effect 的类型级统一协议定义在 packages/effect/src/Unify.ts自 2.0.0 起存在由三个 unique symbol 组成unifySymbol描述某个协议类型在统一widening时应产生什么公开类型typeSymbol存放参与统一的源类型信息ignoreSymbol列出统一时应被忽略的辅助协议条目。核心类型UnifyAUnify.ts对输入A做三件事提取实现了协议携带typeSymbol/unifySymbol的成员取出其unifySymbol声明的目标类型保留那些没有匹配到目标类型的协议成员本身FilterInUnmatched原样保留未实现协议的普通类型FilterOut。最终结果等价于把联合成员各自展开后再重组。运行时配套的Unify.unify则是一个恒等函数Unify.ts只改变推断出的类型不产生任何运行时开销。Effect、Option、Result、Stream、Layer、Match等数据类型都实现了该协议Unify.ts 的模块说明明确列出了这些受益方因此当TaggedUnion.match的分支返回Effect.succeed(...)/Effect.fail(...)时Unify能把Effectnever, never, string | Effectnever, string, never规范化为统一的Effectnever, string, string——这就是 changeset 中 branches to return distinct Effect types that are properly merged 的机制来源。五、同类模式Match模块的返回值早已使用 Unify这次修复让TaggedUnion.match与 Effect 的另一套模式匹配 API——Match模块——在返回类型处理上保持一致。Match对外 API 中Match.valueTags与Match.typeTags的返回类型均写为UnifyReturnTypeP[keyof P]packages/effect/src/Match.ts 与 L518其内部实现位于 packages/effect/src/internal/matcher.ts如 typeTagsreturn (input: I): UnifyReturnTypeP[keyof P] match(input)同样地discriminatorsExhaustive、tagsExhaustive、orElse、orElseAbsurd、result、option、exhaustive等内部函数的返回类型也全部以Unify...收尾matcher.ts。可以推断分支返回类型先取联合、再经Unify规范化是 Effect 中所有模式匹配型 API 的统一类型约定本次 changeset 正是把TaggedUnion.match对齐到这一约定上。六、测试佐证仓库测试 packages/effect/test/schema/Schema.test.ts 对toTaggedUnion与TaggedUnion的行为覆盖相当完整可作为本修复所依赖运行时语义的验证依据判别值收集schema.discriminants按成员扁平化顺序返回支持字符串、数字、UniqueSymbol如[A, b, 1, D]重复判别值抛错对相同字面量包括1与1这类易混情况抛出Duplicate discriminant: ...Schema.test.ts空联合与特殊键空Union得到空discriminants__proto__作为判别值时cases/guards仍能通过Object.hasOwn正确命中Schema.test.tsmatch 双调用形式直接调用schema.match(value, cases)与pipe(value, schema.match(cases))结果一致Schema.test.tsmatchOrElse 回退未命中分支时正确返回orElse结果如() fallback多标签与类成员支持以type等非_tag字段作为判别键也支持Schema.Class成员。七、版本与升级影响变更级别patch向后兼容的类型修复随effect包下一个补丁版本发布对现有使用TaggedUnion.match/matchOrElse的代码运行时行为不变。类型收益升级后分支返回不同类型Effect的代码将获得更精确、可链式调用的推断类型若此前依赖裸联合推断极少见需注意推断结果的收窄。适用范围本修复覆盖Schema.TaggedUnion(...)与Schema.Union([...]).pipe(Schema.toTaggedUnion(tag))两条路径生成的match/matchOrElse二者最终都由toTaggedUnion的同一套TaggedUnionUtils类型提供签名见 Schema.ts。结语一条两行的 changeset 背后是一次将TaggedUnion.match的对齐进 Effect 类型系统统一约定的修复Unify协议把各分支返回不同 Effect 类型的联合结果在编译期正确合并让基于判别联合的模式匹配可以无缝衔接后续的 Effect 链式编程。理解Unify的unifySymbol/typeSymbol/ignoreSymbol协议与TaggedUnion的运行时机制不仅能解释这条变更记录也能让你在自定义数据类型时主动接入这一协议享受同样的类型推断红利。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
