后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载导读MonoidKF是 language-ext 中面向高阶类型higher-kinds的幺半群monoid抽象它继承SemigroupKF将经典幺半群的“单位元 结合二元运算”概念从普通值A提升到KF, A结构这一层。凡实现了MonoidKFtrait 的类型自动获得一个“零/单位元”Empty()以及把两个KF, A结构合并为一个的Combine能力与ApplicativeF、MonadF组合后还能免费获得Filter/Where、Choose、oneOf、some、many、guard等强大的“结构合并/备选alternative折叠”功能。读完本文你将掌握该 trait 的定义与模块函数、底层实现方式、??式的错误传播语义以及如何在实际代码中利用这些免费能力做过滤、选择与解析式组合。一、MonoidK 是什么面向高阶类型的幺半群在经典的代数抽象中MonoidA由两部分构成一个单位元identity / zero例如整数加法中的0一个满足结合律的二元运算associative binary operation例如整数加法中的。MonoidKF做的正是同一件事只不过把操作对象从A提升为KF, A即“被F包裹的高阶类型结构”。正如 MonoidK/README.md 所言“MonoidK 是作用于高阶类型KF, A 而非 A的幺半群”任何实现该 trait 的类型都会获得一个Empty()—— 即“零/单位元”值把两个KF, A结构Combine成一个的能力。在 C# 语法层面trait 通过“带 self-type 约束的静态抽象接口”实现。核心接口定义在 MonoidK.cspublic interface MonoidKM : SemigroupKM where M : MonoidKM { /// summary /// Identity /// /summary [Pure] public static abstract KM, A EmptyA(); }它直接继承SemigroupKM而SemigroupKM定义在 SemigroupK.cs 中只声明了一个静态抽象方法public interface SemigroupKM where M : SemigroupKM { [Pure] public static abstract KM, A CombineA(KM, A lhs, KM, A rhs); }接口上的where M : MonoidKMself-type 约束保证只有“自己实现自己”的类型才能作为高阶参数M这与 language-ext 其他 traitMonadF、ApplicativeF、AlternativeF等的编码风格完全一致EmptyA()被标记为[Pure]表示它是无副作用的纯函数两个核心成员的类型签名都落在KM, A上因此MonoidK本质上是结构层面的幺半群与值层面MonoidA区分开来。二、Combine 的错误传播语义像 ?? 一样“先成功者优先”原文中特别强调了一个重要的直觉很多MonoidKF实现用Combine来“捕获错误并传播”。其行为类似 C# 的??运算符对null的传播如果Combine的第一个参数KF, A失败就直接返回第二个参数如果第一个参数成功就返回第一个参数的结果。这种“短路式选择”语义并非MonoidK的强制规定Combine只需满足结合律即可但在 language-ext 中确实是最常见的实现方式值得使用者了解。下面用三个仓库中的真实实现来印证1. Fin错误优先的短路选择Fin.TraitImpl.cs 是Fin的 trait 实现static KFin, A MonoidKFin.EmptyA() FailA(Errors.None); static KFin, A SemigroupKFin.CombineA(KFin, A ma, KFin, A mb) ma switch { FinA.Succ ma, // 第一个成功 → 直接返回第一个 FinA.Fail (var e1) mb switch { FinA.Succ mb, // 第一个失败、第二个成功 → 返回第二个 FinA.Fail (var e2) FailA(e1 e2), // 都失败 → 错误合并 _ mb }, _ ma };可以看到Empty是一个携带Errors.None的Fail即“无错误的失败”Combine则遵循“第一个成功就取第一个否则尝试第二个都失败则合并错误”的规则——这正是“类似??的错误传播”的典型体现。2. Eff效果型计算的失败回退Eff.Monad.cs 中Eff声明实现了MonoidKEff、AlternativeEff等 trait其实现为static KEff, A MonoidKEff.EmptyA() EffA.Fail(Errors.None); static KEff, A SemigroupKEff.CombineA(KEff, A lhs, KEff, A rhs) lhs.Choose(rhs);Eff的Combine直接委托给Choice.Choose即“第一个成功则取第一个失败则回退到第二个”的备选语义适用于效果effect型计算的错误回退场景。3. Validation失败值本身要求是幺半群Validation.TraitImpl.cs 展示了MonoidK的一个有趣约束ValidationFAIL, A的Empty要求FAIL类型自身实现MonoidFAIL并用其单位元作为失败值static KValidationFAIL, A MonoidKValidationFAIL.EmptyA() MonoidInstanceFAIL.Instance switch { { IsSome: true, Value: { } monoid } Validation.FailIFAIL, A(monoid.Empty), _ throw new NotSupportedException(${typeof(FAIL).Name} must be a Monoid) }; static KValidationFAIL, A SemigroupKValidationFAIL.CombineA( KValidationFAIL, A ma, KValidationFAIL, A mb) ma.As().CombineFirst(mb.As(), SemigroupInstanceFAIL.Instance);而它的Choice.Choose则明确“成功优先”只要ma或mb有一个是Success就返回它两个都失败才返回maValidation.TraitImpl.cs。4. 集合类Combine 即拼接并非所有实现都走“错误回退”路线。例如Lst和Seq这类无错误概念的集合结构其Combine就是纯粹的列表拼接LstLst.TraitImpl.csEmpty为LstA.EmptyCombine为ma.As() mb.As()SeqSeq.TraitImpl.csEmpty为SeqA.EmptyCombine为ma.As() mb.As()。这种多样性正好呼应 README 中的提醒“并非总是这样实现 MonoidK但了解这个模式很有用。”理解你所用的具体类型的Combine语义是正确使用它的前提。三、MonoidK 模块empty 与 combine 系列函数MonoidK的静态模块定义在 MonoidK.Module.cs为使用者提供了统一的入口函数// 单位元F.EmptyA() [Pure] public static KF, A emptyF, A() where F : MonoidKF F.EmptyA(); // 二元结合运算 [Pure] public static KF, A combineF, A(KF, A ma, KF, A mb) where F : MonoidKF F.Combine(ma, mb); // 三元 可变参数版本的 combine [Pure] public static KM, A combineM, A(KM, A mx, KM, A my, KM, A mz, params KM, A[] xs) where M : MonoidKM xs.AsIterable().Fold(combine(combine(mx, my), mz), combine); // 折叠任意 IEnumerable 与 Seq 集合 [Pure] public static KM, A combineM, A(IEnumerableKM, A xs) where M : MonoidKM xs.AsIterable().Fold(M.EmptyA(), combine); [Pure] public static KM, A combineM, A(SeqKM, A xs) where M : MonoidKM xs.Fold(M.EmptyA(), combine);要点说明emptyF, A()只是对 trait 静态成员F.EmptyA()的转发combine的重载覆盖了“二元 / 三元params / IEnumerable / Seq”四种常见形态方便你一次合并多个结构集合形态的combine以M.EmptyA()作为折叠种子再用二元combine依次合并体现了“单位元 结合律”的标准幺半群折叠模式。四、与 Monad/Applicative 组合免费获得的六大功能MonoidKF的价值在于组合。README 明确指出当它与ApplicativeF、MonadFtrait 组合后会提供以下默认功能功能名称作用Filter/filter过滤类型支持MonadFMonoidKF时免费获得过滤能力及WhereLINQ 扩展Choose/choose过滤 映射在过滤的同时对通过的元素进行映射OneOf/oneOf选择给定一组KF, A结构返回第一个成功者Some/some一或多反复求值KF, A并收集A值直到失败为止至少一个必须成功返回KF, SeqAMany/many零或多同上但允许零个成功返回KF, SeqAguard条件失败根据布尔条件使计算继续或短路失败下面逐一结合源码展开。4.1 Filter / Where基于 Bind Empty 的免费过滤Filter与Where的实现位于 MonoidK.Extensions.cs同时模块版filter在 MonoidK.Module.cspublic static KM, A FilterM, A(this KM, A ma, FuncA, bool predicate) where M : MonoidKM, MonadM M.Bind(ma, a predicate(a) ? M.Pure(a) : M.EmptyA()); public static KM, A WhereM, A(this KM, A ma, FuncA, bool predicate) where M : MonoidKM, MonadM M.Bind(ma, a predicate(a) ? M.Pure(a) : M.EmptyA());实现思路非常巧妙谓词不通过时返回EmptyA()通过时返回Pure(a)借助Bind把“失败/空”传播到整个链式计算中。Where是Filter的同义别名用于支持 C# LINQ 语法中where关键字的翻译。这一点在测试中也有大量印证例如 ArrayTests.cs、QueryTests.cs、ScheduleTests.cs 等都直接使用.Filter(...)链式过滤。4.2 Choose过滤与映射一步完成Choose在过滤的同时完成类型转换public static KM, B ChooseM, A, B(this KM, A ma, FuncA, OptionB selector) where M : MonoidKM, MonadM M.Bind(ma, a selector(a).Match(Some: M.Pure, None: M.EmptyB));选择器返回OptionBSome时用M.Pure提升为结构值None时用M.EmptyB()表示“丢弃”因此Choose既能过滤None被丢弃又能映射Some(b)被保留并转型是FilterMap的合体测试用例可见于 EnumerableTTests.csAsIterable(input).Choose(x x)。4.3 oneOf返回第一个成功者oneOf在 Alternative.Prelude.cs 中定义约束是AlternativeF而Alternative是MonoidK的强化形态/// 给定一组 applicative functor返回第一个成功者。 /// 如果全部失败返回最后一个。 [Pure] public static KF, A oneOfF, A(params KF, A[] ms) where F : AlternativeF Alternative.choice(ms); [Pure] public static KF, A oneOfF, A(SeqKF, A ms) where F : AlternativeF Alternative.choice(ms);语义要点依次尝试每个候选返回第一个“成功”的如果全部失败返回最后一个而不是空扩展方法形态OneOf(this SeqKF, A)在 Alternative.Extensions.cs 中底层统一走Alternative.Choice真正决定“成功”判定的是具体类型对Choice的实现如前面Lst/Fin/Eff/Validation各自的规则。4.4 some / many重复求值直到失败some与many是解析器风格Parsec 风格的功能定义于 Alternative.Module.cs/// 一个或多个……反复运行 applicative functor 并收集结果直到失败。 /// 只要产生过至少一个元素就必然成功。 [Pure] public static KF, SeqA someF, A(KF, A fa) where F : AlternativeF F.Some(fa); /// 零个或多个……反复运行 applicative functor 并收集结果直到失败。 /// 永远成功。 [Pure] public static KF, SeqA manyF, A(KF, A fa) where F : AlternativeF F.Many(fa);注意实现细节中有一个重要的性能/正确性提醒源码注释使用some/many时F的 trait 实现必须覆盖Apply带Func惰性的版本与ApplyLazy否则可能引发栈溢出stack-overflow。另外some要求至少成功一次对应正则的many允许零次成功对应正则的*两者都返回KF, SeqA——收集到的值放入不可变的SeqA。与之配套的还有endBy/endBy1、sepBy/sepBy1、manyUntil/someUntil等组合子见 Alternative.Module.cs它们把some/many与分隔符、终止条件组合构成完整的“解析式组合”工具箱。4.5 guard条件失败guard定义在 Prelude.guard.cs/// Guard against continuing an applicative expression [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static KF, Unit guardF(bool flag) where F : MonoidKF, ApplicativeF flag ? F.Pure(unit) : F.EmptyUnit();它的代数语义非常清晰guard(true) Applicative.pure继续计算产出Unitguard(false) Alternative.empty产生“空”使后续计算短路失败。这也就是模块版MonoidK.guardMonoidK.Module.cs所注释的恒等式。此外 Prelude 还提供带错误值的guard(bool, Error)、guardE(bool, E)、guardIO等重载Prelude.guard.cs用于需要携带具体失败信息的场景。五、MonoidK 在其它 trait 谱系中的位置与派生机制5.1 与 Alternative / Choice / Applicative / Monad 的关系从仓库源码可以清晰看到 trait 之间的层级关系MonoidKF继承SemigroupKF在SemigroupK之上补充了单位元EmptyA()MonoidK.csAlternativeF是对MonoidKF的强化形态在多个类型实现中MonoidKF.Empty与AlternativeF.Empty指向同一实现如 Fin.TraitImpl.cs、Lst.TraitImpl.cs、Seq.TraitImpl.csChoiceF提供二元“选择”语义Choose是oneOf/Combine在错误回退型类型上的底层支撑Choice/Choice.Module.cs 中同样有some/many。换言之MonoidK是Alternative家族的地基之一。README 中所列的oneOf、some、many虽在Alternative模块中实现但它们的本质正是“幺半群的单位元 结合结构 错误传播”的组合运用README 将其归入MonoidK的组合收益正是这个道理。5.2 Deriving.MonoidK子类型自动继承仓库为“由子类型Subtype派生超类型Supertypetrait”的场景提供了派生版本见 Deriving/MonoidK.cspublic interface MonoidKSupertype, Subtype : MonoidKSupertype, SemigroupKSupertype, Subtype where Supertype : MonoidKSupertype, Subtype where Subtype : MonoidKSubtype { static KSupertype, A MonoidKSupertype.EmptyA() Supertype.CoTransform(Subtype.EmptyA()); }它把子类型的EmptyA()通过CoTransform协变到超类型实现“子类型实现了MonoidK超类型自动继承”的效果减少样板代码。5.3 谁实现了 MonoidK通过检索仓库实现了MonoidKF的类型遍布核心库包括但不限于效果/单子类型EffEff.Monad.cs失败/备选单子Fin、FinT、Validation、ValidationT分别在各自的 Trait 目录下不可变集合Arr、Lst、Seq、Set、Map、HashMap、HashSet、Iterable、Iterator等分布在 Immutable Collections 各子目录的TraitImpl文件中。这种“同一套 API、无数种具体语义”的设计正是高阶 trait 抽象的核心价值你只写一次Filter/Choose/guard的调用代码却能作用于所有实现MonoidK的结构。六、实践要点与注意事项先确认目标类型的 Combine 语义错误型类型Fin/Eff/Validation的Combine通常是“先成功者优先”集合型类型Lst/Seq的Combine则是拼接。使用前查看对应TraitImpl避免语义误判。Filter/Where 的约束where M : MonoidKM, MonadM即你的类型必须是“既是幺半群又是单子”的高阶类型才能调用免费过滤。guard 的约束where F : MonoidKF, ApplicativeF配合Monad的Bind才能在 do 记法/LINQ 表达式中实现短路式条件失败。some/many 的栈溢出风险使用它们时目标类型的 trait 实现必须覆盖惰性的Apply/ApplyLazy否则递归求值可能栈溢出见 Alternative.Module.cs 的注释。Validation 的 Empty 要求 FAIL 是 MonoidValidationFAIL, A的Empty依赖FAIL实现MonoidFAIL否则会抛出NotSupportedExceptionValidation.TraitImpl.cs。oneOf 全失败的返回值oneOf在全部候选都失败时返回最后一个候选而非“空”这在编写回退链fallback chain时是有意为之的行为。七、总结MonoidKF用约 20 行接口定义撬动了一大族实用的高阶类型能力在结构层面提供“单位元 结合律”的幺半群契约EmptyCombine通过Combine的“错误传播 / 先成功者优先”实现获得类似??的回退语义Fin、Eff、Validation或纯拼接语义Lst、Seq与MonadF/ApplicativeF组合后免费获得Filter/Where、Choose、oneOf、some、many、guard六大功能将 Parsec 风格的“备选结构折叠alternative structure coalescing”概念完全泛化到任意高阶结构。理解MonoidKF你就能在 language-ext 中写出“一次抽象、处处复用”的声明式代码——这正是本项目“come and get declarative!”设计哲学的又一具体体现。赞分享后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载相关推荐深入理解 Cats 的 MonoidK面向类型构造器的通用幺半群Universal Monoid深入理解 Cats 的 MonoidK面向类型构造器的通用幺半群Universal Monoid 导读 MonoidK 是 Cats 中面向 一参数类型后端fpinscala 幺半群实战用 Scala 3 given 定义积幺半群productMonoidfpinscala 幺半群实战用 Scala 3 given 定义积幺半群productMonoid 导读 本文以《Functional Programm示例工程fp-ts Predicate 模块完全指南类型安全的布尔谓词组合、逆变与幺半群实例fp ts Predicate 模块完全指南类型安全的布尔谓词组合、逆变与幺半群实例 导读 本文以 fp ts 仓库中 Predicate 模块文档 http开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?