首页 / 资讯中心 / 文章详情

language-ext 函数式编程:Choice trait 与失败/选择传播机制深度解析

language-ext 函数式编程:Choice trait 与失败/选择传播机制深度解析 ★ FEATURED ARTICLE
后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载导读在 language-ext 这个 C# 纯函数式编程框架中Choicetrait 定义了失败与选择的传播语义当某个计算失败时如何优雅地回退到另一个候选计算。本文以 Choice trait 官方文档 为核心深入讲解Choose与SemigroupK.Combine的语义分工|与两个操作符的定位、四种 Choice 定律及其测试方法并结合仓库源码展示 Option、Eff、IO、Seq、Lst 等类型的具体实现与some/many等派生能力。读完本文你将掌握在 language-ext 中利用 Choice trait 编写先尝试、失败再回退逻辑的完整方法论。一、Choice trait 是什么失败与选择的传播ChoiceF的核心作用是让类型F支持失败与选择的传播——选择的具体含义取决于类型本身。以 接口定义 为证public interface ChoiceF where F : ChoiceF { // 如果 fa 成功则返回 fa否则返回 fb static abstract KF, A ChooseA(KF, A fa, KF, A fb); // 重载第二个候选以 MemoF, A惰性记忆化形式传入 static abstract KF, A ChooseA(KF, A fa, MemoF, A fb); }几个值得注意的细节F-bounded 约束where F : ChoiceF是典型的自身类型约束保证 trait 方法可以直接通过类型参数F静态调用F.Choose(fa, fb)这与 language-ext 全库统一的 HKThigher-kinded type表达方式KF, A一致。两个重载第二个重载接收MemoF, A允许第二个候选延迟求值、只在使用时才计算这在某些副作用场景下可以避免不必要的执行。Memo是 language-ext 中的记忆化容器Deriving/Choice.cs 中可以看到Memo.transform的使用说明 Memo 可以在不同类型间做自然变换。KF, A是 language-ext 对未完全应用的泛型类型构造器的封装。例如OptionA实现了KOption, A那么Option这个类型构造器就可以作为F传给ChoiceOption。二、Choose 与 Combine|与的语义分工Choice本身是SemigroupK的成员广义半群但 README 中明确强调它定义了独立的Choose方法而不是依赖SemigroupK.Combine——而SemigroupK.Combine如今拥有一个默认实现即内部调用Choose。这一设计的目的在于为Choose建立选择传播这一全新的语义含义使其区别于Combine更宽泛的合并含义同时也允许Choose与Combine在具体类型上各自独立实现。README 给出的思维方式非常精炼Choose失败/选择传播操作符|Combine连接/组合/加法操作符在源码中可以找到这两个操作符的落地实现|操作符lhs.Choose(rhs)并有一个接收PureA的便捷重载将纯值提升进F后参与选择。操作符lhs.Combine(rhs)。以集合类型为例可以清晰看出二者的差别。Seq 的 TraitImpl 中// 选择第一个非空序列胜出 static KSeq, A ChoiceSeq.ChooseA(KSeq, A ma, KSeq, A mb) ma.As().IsEmpty ? mb : ma; // 合并序列直接拼接真正的加法语义 static KSeq, A SemigroupKSeq.CombineA(KSeq, A ma, KSeq, A mb) ma.As() mb.As();可见对Seq而言ma | mb表示若 ma 为空则取 mb而ma mb表示拼接两个序列。语义完全不同却又共存在同一类型上。Lst 的实现 完全一致。而某些类型没有独立的Combine实现则使用默认实现内部委托给Choose。例如 Eff 的 TraitImplstatic KEff, A SemigroupKEff.CombineA(KEff, A lhs, KEff, A rhs) lhs.Choose(rhs);README 中有一条配套约定任何支持Choicetrait 的类型都应实现|操作符以方便选择/失败传播如果该类型对Combine有独立实现而不是接受默认实现则还应实现操作符。三、Choice 定律ChoiceLaw如何验证你的实现为了让选择行为可预期、可测试language-ext 在 Choice.Laws.cs 中定义了ChoiceLawF并给出了四条核心定律。README 中的表述如下choose(Pure(a), Pure(b)) Pure(a) // 左捕获定律left catch choose(Fail, Pure(b)) Pure(b) // 左零定律left zero choose(Pure(a), Fail) Pure(a) // 右零定律right zero choose(Fail [1], Fail [2]) Fail [2] // 失败时取最后一个失败对照源码ChoiceLawF需要F : ChoiceF, ApplicativeF并提供两个入口assert(KF, int failure, FuncKF, int, KF, int, bool? equals null)断言定律成立失败时抛出Errorvalidate(...)返回ValidationError, Unit便于组合与延迟处理。validate内部依次执行三条定律的校验见 Choice.Laws.csleftZeroLawchoose(empty, pure(x)) pure(x)——失败值放左边时必须回退到右边的纯值rightZeroLawchoose(pure(x), empty) pure(x)——失败值放右边时左边的纯值必须胜出leftCatchLawchoose(pure(x), pure(y)) pure(x)——两个纯值相遇永远取左边。关键前提Equals 的取舍ChoiceLaw的比较默认依赖KF, *派生类型实现Equals。README 与源码注释都强调如果你的 Alternative 结构没有实现Equals则必须通过可选参数equals提供比较函数否则定律无法验证。典型的例子来自 ChoiceLawTests.cs[Fact] public void Eff() { bool eq(KEff, int vx, KEff, int vy) vx.Run().Equals(vy.Run()); ChoiceLawEff.assert(errorEff, int(Errors.None), eq); } [Fact] public void IO() { bool eq(KIO, int vx, KIO, int vy) vx.RunSafe().Equals(vy.RunSafe()); ChoiceLawIO.assert(errorIO, int(Errors.None), eq); }而纯数据类型的比较则简单直接如Arr、Lst、Seq[Fact] public void Arr() ChoiceLawArr.assert(Arrint.Empty); [Fact] public void Seq() ChoiceLawSeq.assert(Seqint.Empty);ChoiceLawTests覆盖了Arr、HashSet、Iterable、Lst、Seq、Eff、IO、Either、Fin、Option、Try、Validation及其各类 TransformerOptionT、EitherT、FinT、TryT、ValidationT等合计十余个测试点——这意味着 Choice 定律在整个框架中被广泛遵守。另外README 提到 ChoiceLaw 同时测试Applicative与Functor定律这与ChoiceLawF的约束F : ChoiceF, ApplicativeF相呼应选择能力建立在应用函子与函子能力之上而非孤立存在。四、从接口到 APIchoose / Some / Many 的使用入口Choicetrait 的能力通过四层 API 暴露给调用方1. 静态模块函数Choice.Module.csChoice.Module.cs 提供核心入口public static KF, A chooseF, A(KF, A fa, KF, A fb) where F : ChoiceF F.Choose(fa, fb);2. Prelude 函数Choice.Prelude.csPrelude 层 提供同名全局函数choose以及两个重要派生函数someF, A(fa)一个或多个——重复运行 applicative 并收集结果直到失败只要至少产出一个元素就整体成功manyF, A(fa)零个或多个——同上但允许零结果永远成功。这两个函数把选择/失败传播升级为重复尝试直到失败的组合子是解析器、读取器等场景的常见构建块。它们的实现Choice.Module.cs是一个相互递归的经典结构public static KF, SeqA manyF, A(KF, A fa) where F : ChoiceF, ApplicativeF { return many_v(); KF, SeqA many_v() F.Choose(some_v(), F.Pure(SeqA())); // 失败时返回空序列 KF, SeqA some_v() AppendA.cons.Map(fa).Apply(memoK(many_v)); // 一个元素 继续 many }some与many互相引用、彼此终止正是借助Choose的失败即回退语义完成的many内部用F.Choose(some_v(), F.Pure(SeqA()))表达继续尝试失败就返回空序列。重要警告源码注释明确说明F的 applicative 实现必须重写带Func惰性的Apply对many则是ApplyLazy否则这种相互递归可能导致栈溢出。这是实现 Choice 派生功能时的关键约束。3. 扩展方法Choice.Extensions.cs扩展方法层 提供链式调用风格KF, A fa ...; var result fa.Choose(fb); // 选择 var multi fa.Many(); // 零或多个 var oneOr fa.Some(); // 一个或多个4.|操作符如前所述|操作符 是Choose的中缀语法糖也是 README 推荐的首选表达方式。五、Alternative traitChoice 的默认实现与派生组合子在 trait 层级体系中Choice并非孤立存在AlternativeF : ChoiceF, ApplicativeF见 Alternative.Trait.cs。因此凡是 Alternative 都是 Choice。Alternative在此基础上补充了EmptyA()identity以及一组基于Choose的默认实现public static virtual KF, A ChoiceA(in SeqKF, A ms) { if(ms.IsEmpty) return F.EmptyA(); var r ms[0]; foreach (var m in ms.Tail) { r | m; // 依次用 | 折叠返回第一个成功者 } return r; }该重载以及ReadOnlySpanKF, A版本Alternative.Trait.cs接受一组候选结构返回第一个成功者如果全部失败则返回最后一个与choose(Fail[1], Fail[2]) Fail[2]定律一致。更重要的是Alternative基于Some/Many派生出一整套解析器风格的组合子同为默认虚实现见 Alternative.Trait.cs组合子语义Option(value, fa)尝试fa失败且未消耗任何东西则返回默认值valueSepBy(fa, sep)/SepBy1零/一个或多个fa彼此用sep分隔SepByEnd(fa, sep)/SepByEnd1同上但结尾允许/要求一个分隔符EndBy(fa, sep)/EndBy1零/一个或多个fa以sep分隔并以sep结尾ManyUntil(fa, end)/SomeUntil零/一个或多个fa直到end成功为止end的结果被丢弃ManyUntil2/SomeUntil2变体保留end结果SkipMany(fa)/SkipSome(fa)零/一个或多个fa丢弃全部产出Skip(n, fa)恰好处理n次fa并丢弃结果SkipManyUntil(fa, end)/SkipSomeUntil跳过若干fa直到end成功返回end的值Either(fa, fb)把两个候选取其一并把成功的一方提升为EitherA, B源码注释说明这些组合子起源于解析器组合子parser combinator但可应用于任何Alternative结构。这也解释了 Choice 在整个框架中的深层角色它是 language-ext 内部Parsec/Megaparsec等解析子系统的代数基础之一。六、具体类型实现从 Option 到 Eff 的 Choose 语义不同结构对失败的定义不同因此Choose的实现也各具特色。以下从源码逐一拆解。OptionSome 胜出Option.TraitImpl.csstatic KOption, A ChoiceOption.ChooseA(KOption, A ma, KOption, A mb) ma.As() switch { { IsSome: true } ma, _ mb };None即失败Some(x)即成功第一个是Some就直接返回否则回退到第二个。同时AlternativeOption.Empty返回OptionA.None同文件 L66-L67两者配合构成完整的选择语义。Eff / IO异常与错误即失败Eff 的实现 将Choose委托给内部effect.Choose而AlternativeEff.Empty定义为EffA.Fail(Errors.None)L55-L56——失败即错误Error。IO 的实现 则直接展开其内部状态机static KIO, A ChoiceIO.ChooseA(KIO, A fa, KIO, A fb) fa switch { IOEmptyA fb, IOFailA fb, _ new IOCatchA, A(fa, _ true, _ fb, null, pure) };若fa是IOEmpty或IOFail直接回退到fb否则构造一个IOCatch包装——把fb作为 catch 分支挂接到fa上待运行时若fa抛错则自动捕获并切换。这展示了Choose在效果系统effect system中的真实威力它把异常捕获 回退提升为代数操作声明式地表达而不是散落在 try/catch 中。集合类型非空即胜出Seq、Lst、Arr、Set、HashSet、Iterable、Iterator等集合类型把空集合视为失败// Seq static KSeq, A ChoiceSeq.ChooseA(KSeq, A ma, KSeq, A mb) ma.As().IsEmpty ? mb : ma;这也呼应了some/many在集合上的直觉反复取元素直到取不到为止。Deriving.Choice子类型间的自然变换Deriving/Choice.cs 提供了一个特殊的二型接口ChoiceSupertype, Subtype用于借用子类型的 Choice 实现通过Natural/CoNatural变换把Supertype的输入转换给Subtype处理再把结果转换回来static KSupertype, A ChoiceSupertype.ChooseA(KSupertype, A fa, KSupertype, A fb) Supertype.CoTransform(Subtype.Choose(Supertype.Transform(fa), Supertype.Transform(fb)));这是 language-ext 处理 Monad Transformer 与类型提升的通用技法EffMinRT与Eff之间Eff.Monad.cs正是通过这一机制共享 Choice 能力。七、实战示例选择传播的三种写法下面以Option为例把同一逻辑写成三种等价的调用形式源码依据Choice.Module.cs、Choice.Operators.cs、Option.Operators.Choice.csusing static LanguageExt.Prelude; // 候选三个来源逐级回退 Optionint primary Some(42); Optionint secondary None; Optionint fallback Some(0); // 写法一Prelude 函数 var r1 choose(primary, choose(secondary, fallback)); // 写法二扩展方法 var r2 primary.Choose(secondary).Choose(fallback); // 写法三| 操作符推荐最贴近失败即回退的心智模型 var r3 primary | secondary | fallback; // r1 r2 r3 Some(42)运行期行为与ChoiceOption的实现一致primary是Some因此整条链直接返回Some(42)secondary与fallback均不会被选中若把primary换成None结果依次落到secondary、fallback。再结合some/many做重复尝试// 连续读取配置直到失败至少一次 KOption, Seqstring keys some(ReadKey()); // 连续读取直到失败允许零次永远成功 KOption, Seqstring allKeys many(ReadKey());如果要把尝试 A、失败则回退到 B写成结构化类型间的切换保留两边各自的值类型可以用Alternative.Either// 返回第一个成功的候选并标记它来自哪一侧 KOption, EitherA, B result OptionExtensions.Either(candA, candB);八、总结与最佳实践语义先行|表达选择/失败回退表达合并/拼接。写代码时先想清楚你要的是哪一种二者在集合类型上的行为截然不同非空胜出 vs 拼接。实现 Choice 的清单实现Choose的两个重载实现|操作符若Combine有独立语义则实现用ChoiceLaw.assert必要时传入equals验证实现是否满足四条定律。注意栈安全使用some/many或任何基于它们派生的组合子时确认F的Apply带惰性已正确重写否则可能栈溢出Choice.Module.cs 注释。能力分层Choice→Alternative加入Empty与成套的解析组合子→ 具体类型Option、Eff、IO、集合等。按需选择最合适的抽象层级。验证手段仓库的 ChoiceLawTests.cs 是现成的参考——为自定义类型添加同名测试即可快速确认你的 Choice 实现符合框架语义。相关源码索引Choice 官方文档Choice trait 接口定义Choice 定律实现Choice 模块函数choose/some/manyChoice 操作符|Choice 扩展方法Choice Prelude 函数Alternative trait默认实现与派生组合子SemigroupK 与操作符Option 的 Choose 实现Eff 的 Choose 实现IO 的 Choose 实现Seq 的 Choose 与 Combine 对比Lst 的 Choose 实现Choice 定律测试集赞分享后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载相关推荐Finagle 失败信号机制深度解析从 FailureFlags 到跨协议失败传播Finagle 失败信号机制深度解析从 FailureFlags 到跨协议失败传播 导读 Finagle 作为一个容错的、协议无关的 RPC 系统其内部有后端RPC框架开源项目推荐Language-ext —— 扩展C的函数式编程维度开源项目推荐Language ext —— 扩展C 的函数式编程维度 项目基础介绍与编程语言 Language ext 是一个专为C 设计的开源项目旨在通过后端iniparser错误处理与调试解决常见解析问题的10个技巧iniparser错误处理与调试解决常见解析问题的10个技巧 iniparser是一款轻量级的ini文件解析库广泛应用于C语言项目中。在使用过程中开发者经后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站