language-ext 备选值单子(Alternative Monads)完全指南:从 `Either` 原型理解 Fin、Try、Option 与 Validation
后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载导读本文聚焦 C# 函数式编程框架 language-ext 中一整族被称为 Alternative Monads备选值单子的核心类型。它们都围绕一个统一心智模型展开单子内部有一个绑定值bound value和一个备选值alternative value备选值通常用于承载错误、缺省值或失败信息。读完本文你将理解EitherL, R为何是这一族的原型掌握FinA、TryA、OptionA、NullableA、ValidationF, A各自的语义定位、构造方式与适用场景并能基于 Alternative trait 的源码 理解选择/或Choice组合的底层实现。什么是备选值单子元组是 AND备选单子是 OR先看一个元组(int, string)这个类型同时表示一个int和AND一个string——两个值都必须存在。再看EitherL, R单子它的值要么是Left要么是Right——是L或ORREitherint, string表示要么int要么string。从类型论角度看Either是元组的自然对偶dual元组是乘积Either是和。在Either中Right值被视为单子的绑定值bound valueLeft值被视为备选值alternative value。语言-ext 中所有其他备选值单子都可以看作是Either的派生或特化形式——这是理解整族类型的关键钥匙。备选值单子家族一览主文档用一张表完整概括了这一族的六个成员每种类型都用一个备选值类型 绑定值类型的二元结构来表达类型备选值类型Alternative Value Type绑定值类型Bound Value Type说明NotesEitherL, RLR原型L通常是错误载体但并非必须FinAErrorA等价于EitherError, ATryAErrorA等价于EitherError, A且会捕获异常OptionANoneA等价于EitherUnit, ANullableAnullA等价于EitherUnit, AValidationFail, SuccFailSuccFail必须是MonoidFail才能收集错误重要提示备选值通常被用来承载错误但这不是强制要求。备选值可以是任何你想要承载的用途。这一点在 备选值单子主文档 中被反复强调——不要一看到Left/None/Fail就认定它一定是异常。原型EitherL, R绑定值、备选值与短路语义Either是这一族所有类型的母体。在源码中它被定义为抽象 record由Left和Right两个 sealed record 子类构成两种状态见 Either.cs 与 Either.Left.csEitherstring, int ma Leftstring, int(this is an error); Eitherstring, int mb Rightstring, int(123);源码中还提供了免去双泛型参数的便捷类型Left和Right配合隐式转换构造更简洁见 Either/README.mdEitherstring, int ma Left(this is an error); Eitherstring, int mb Right(123);如果类型推导遇到困难显式写出泛型参数即可。Either的语义可以从 Either.cs 的注释中得到权威解读当Either处于Left状态时它会**取消cancel**后续的bind、map等计算——即带消息的提前退出early out, with a message而Option的None是无消息的提前退出early out, but no message。trait 实现印证短路行为从 Either.TraitImpl.cs 可以看到MonadEitherL的Bind实现ma switch { EitherL, A.Right (var r) f(r), // 只有 Right 才继续绑定 EitherL, A.Left (var l) LeftB(l), // Left 直接短路返回 _ throw new NotSupportedException() };这就是Left 短路的源码级证据一旦进入Left后续计算不再执行。选择Choice|与||运算符Either实现了|运算符其注释明确指出必须存在以支持operator true工作Either.cs底层调用Choose。而ChoiceEitherL.Choose的实现是Right优先否则取第二个Either.TraitImpl.csstatic KEitherL, A ChoiceEitherL.ChooseA(KEitherL, A ma, KEitherL, A mb) ma is EitherL, A.Right ? ma : mb;在 EitherTests.cs 的测试中可以看到实际用法var x Rightstring, int(1) || Rightstring, int(2) || Leftstring, int(error);特化一FinA——EitherError, A的免泛型便捷别名Fin的定位在源码注释中写得很直白Equivalent ofEitherError, A; CalledFinbecause it is expected to be used as the concrete result of a computationFin.cs。也就是说它功能上与EitherError, A完全一致只是免去了双泛型参数的书写负担非常适合作为计算的具体结果类型。构造方式见 Fin/README.mdFinint ma Pure(123); Finint mb Fail(Error.New(Error!));Fin同样具备Succ/Fail两种状态判断IsSucc、IsFail并通过MatchB(FuncA, B Succ, FuncError, B Fail)完成分支消费Fin.cs。特化二TryA——捕获异常的EitherError, ATry的源码实现是一条record TryA(FuncFinA runTry)注释明确Trymonad which allows for an optionalErrorresult and catches exceptions, converting them toErrorTry.cs。它把异常这种命令式错误机制函数化延迟求值通过runTry委托运行时把抛出的异常转换为Error从而把可能抛异常的代码包装进纯函数式管道。从 Try.cs 可以看到它与Fin的天然亲和public B MatchB(FuncA, B Succ, FuncError, B Fail) this.Run().Match(Succ, Fail); public A IfFail(FuncError, A Fail) Match(identity, Fail);Try的Match直接委托给Run()得到的FinA印证了Try 会捕获异常的 Fin这一等价关系。同时MapFail可通过Catch变换ErrorTry.cs。特化三OptionA——None作为备选值Option的语义是要么A要么None。它是对null表示无值这一做法的替代。null的隐患在于你依然可以对引用类型.进其成员接口每个引用类型都埋着一颗定时炸弹Option/README.md。即便 C# 引入了可空引用类型nullable references特性仍然存在引用类型问题的边界场景更关键的是用泛型类型OptionA可以让可选性不关心值是值类型还是引用类型。源码中OptionA被实现为readonly structNone就是默认值Option.csinternal readonly A? Value; internal readonly bool isSome; public static readonly OptionA None default;构造方式见 Option/README.mdOptionint ma Some(123); Optionint mb None;需要注意OptionA是默认版本其Some情况下不允许null如果想允许null应使用OptionUnsafeA。此外Option在 LINQ 表达式或Map调用中会自动检查None值这让可选值及其对后续代码的影响变成全声明式的。特化四NullableA——把NullableT变成单子.NET 内建的NullableT本身并非单子但 language-ext 为其提供了扩展使其可以像其他单子一样用于 LINQ 表达式。同时提供了自然变换natural transformation函数把可空值转成其他类型Nullable/README.mdint? x ...; Optionint mx x.ToOption();它与OptionA在备选值单子表中等价于EitherUnit, A——null即None。特化五ValidationFail, Succ——唯一能收集错误的备选值单子Validation的源码定位是LikeEitherbut collects multiple failed valuesValidation.cs。它与其他备选值单子的本质区别在于Fail类型必须实现MonoidFail其泛型参数 F 需要满足 Monoid 律这样才能把多个失败值拼接收集起来。为什么Fail必须是Monoid主文档明确指出Fail必须是MonoidFail才能收集错误。源码层面Validation的 trait 实现用MonoidInstanceFAIL来构造Alternative.Empty用SemigroupInstanceFAIL来组合失败值Validation.TraitImpl.csstatic KValidationFAIL, A AlternativeValidationFAIL.EmptyA() MonoidInstanceFAIL.Instance switch { { IsSome: true, Value: { } monoid } Validation.FailIFAIL, A(monoid.Empty), _ throw new NotSupportedException(${typeof(FAIL).Name} must be a Monoid) };而失败值的实际合并逻辑CombineFirst在 Validation.Extensions.cs 中当两侧都是Fail时调用semi.Combine(lhs.FailValue, rhs.FailValue)把错误合并只要有一侧Success就优先保留成功。一个值得注意的运行时约束源码注释揭示了一个容易踩的坑Validation.cs如果传入的F类型没有派生自MonoidF或SemigroupF会抛出TypeLoadException。这是运行时错误而非编译期约束错误因为MonoidF/SemigroupFtrait 是 ad-hoc 解析的。之所以故意不把编译期约束加上是为了能够实现Coproduct、Bifunctor、Bimonad等 trait从而免费获得如Partition等额外功能。因此使用ValidationF, A时务必让F派生自MonoidF或SemigroupF视所需功能而定。测试佐证多个错误被收集ValidationTests.cs 中ExpiredAndInValidCreditCardNumberTest是一个极佳的实战演示对信用卡号同时执行仅允许数字不超过 16 字符未过期三项校验三个校验全部失败时res.IsFail为 true且Match的Fail分支中errors.Count 3——三个错误被完整收集并按序携带Assert.True(errors.Count 3); Assert.True(errors.Head.Message only numbers are allowed); Assert.True(errors.Tail.Head.Message can not exceed 16 characters); Assert.True(errors.Tail.Tail.Head.Message card has expired);这正是Validation与Either的最大分野Either是第一个错误即短路Validation是收集所有错误再统一处理适合表单校验、批量校验等场景。备选值单子的共同抽象Alternativetrait 与Choice从源码结构看这一族类型共享了 trait 抽象。AlternativeF继承自ChoiceF和ApplicativeFAlternative.Trait.cspublic interface AlternativeF : ChoiceF, ApplicativeF where F : AlternativeF { public static abstract KF, A EmptyA(); }trait 的默认实现提供了一整套基于选择的组合子其注释提到这些特性最早源自解析器组合子parser combinator领域但可应用于任何Alternative结构包括Choice——给定一组 applicative functor返回第一个成功的若全部失败则返回最后一个Alternative.Trait.csSome/Many——重复运行直到失败收集结果至少一个 / 零个及以上Option(value, fa)——fa失败时返回默认值valueManyUntil/SomeUntil——反复应用直到终止结构成功SepBy/SepBy1/SepByEnd——按分隔符解析零个、一个或多个值SkipMany/SkipSome/Skip(n, fa)——丢弃结果的重复执行。Either、Option、Validation等类型都在各自的Trait目录下实现了这些抽象如 Either.TraitImpl.cs、Validation.TraitImpl.cs这也是所有备选值单子都是Either的派生或特化这一说法的实现层印证。选型建议与总结需求场景推荐类型理由通用成功/失败携带错误结果EitherL, R原型最灵活L可承载任意错误类型作为计算结果的便捷错误返回FinAEitherError, A的免泛型别名语义明确包裹可能抛异常的代码TryA捕获异常并转Error延迟求值表示可能有值也可能没有OptionA对null的纯函数式替代Some禁null与既有NullableT代码对接NullableA让int?也能进 LINQ 管道可转Option表单校验、批量收集错误ValidationF, A收集全部失败值F须为MonoidF这一族类型共同诠释了 language-ext 的核心设计哲学把失败/缺省/备选从隐式的异常流和 null 检查中提取出来变成显式的、可组合的、声明式的类型。理解绑定值 备选值这个统一模型之后无论是阅读 备选值单子主文档、浏览各类型目录下的Trait/Operators/Extensions源码还是编写自己的错误处理管道都能快速对号入座。赞分享后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载相关推荐language-ext 中的 Fin 单子替代 EitherError, A 的轻量错误处理方案language ext 中的 Fin 单子替代 EitherError, A 的轻量错误处理方案 Fin 是 C 函数式编程框架 language ext后端Java异常处理的终极解决方案Vavr Try、Either、Option类型完全指南Java异常处理的终极解决方案Vavr Try、Either、Option类型完全指南 Vavr是一个强大的Java函数式编程库通过Try、Either、O开发工具告别空引用异常Language-Ext的Option与Either错误处理范式告别空引用异常Language Ext的Option与Either错误处理范式 在C 开发中空引用异常NullReferenceException如同潜后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考