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

language-ext 中的 Class Instances:用 C 实现 Haskell 式类型类实例与 ad-hoc 多态

language-ext 中的 Class Instances:用 C 实现 Haskell 式类型类实例与 ad-hoc 多态 ★ FEATURED ARTICLE
后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载本篇技术指南聚焦 language-ext 的Class Instances类实例体系它如何在 C# 中复刻 Haskell 的 type class instances如何借助「静态抽象接口成员」这一现代 C# 特性实现长期被认为不可能的 ad-hoc polymorphism特设多态以及Eq、Ord、Num、Bool、Monoid等 trait 与EqInt、TInt、TString等实例之间如何配对工作。读完本文你将掌握 language-ext 中泛型数值/比较/布尔运算的底层机制并能在自己的泛型代码中正确选用对应的 class instance。注意正如仓库在 Class Instances 目录说明 开篇的警告——如果你刚接触这个库或者刚接触函数式编程这里几乎可以肯定不是浏览的起点。class instances 建立在 trait 体系之上建议先熟悉 Traits 目录 中的概念再深入本主题。一、Class Instances 是什么在 C# 中复刻 Haskell 的 type class instances在 Haskell 中type class类型类定义了某一族类型共有的行为契约而instance实例则是为某个具体类型提供该契约的具体实现。language-ext 的Class Instances目录LanguageExt.Core/Class Instances就是这一思想的 C# 移植一组以具体类型如int、string、bool、decimal为参数的结构体struct实现对应的 trait 接口从而把「某种类型支持某种操作」这一事实显式地建模为类型。用原文档中的话说Class-instances try to replicate the instances of Haskell in C#. They pair with the traits using ad-hoc polymorphism.类实例试图在 C# 中复刻 Haskell 的 instances它们通过 ad-hoc 多态与 trait 配对。Trait定义操作契约如EqA定义相等性测试位于 LanguageExt.Core/TraitsClass Instance为具体类型A提供该契约的落地实现如EqInt为int提供相等性测试位于 LanguageExt.Core/Class Instances。两者通过「泛型类型参数 约束」配对trait 是带类型参数的接口instance 是实现了该接口的具体类型。当你写一个约束为where EQ : EqA的泛型函数时调用方传入哪个 instance就决定了采用哪种相等性语义——这正是 ad-hoc特设的体现同一段泛型代码可以针对不同实例表现出不同行为。二、为什么 C# 原生缺少这样的抽象装箱问题的来龙去脉原文档给出了一个非常具体的动机场景我们一直希望 BCL 能提供INumeric这样的接口让int、long、double等数值类型共享统一的泛型数值操作。但假如我们这样写INumeric Add(INumeric x, INumeric y) x y;问题立刻出现装箱boxing。因为INumeric是引用类型接口把int这样的值类型当作INumeric传入参数、返回结果都会在托管堆上装箱拆箱。这比直接操作值类型要慢原文档措辞是 well, slower。原文档推测这正是 BCL 团队没有给数值类型加统一接口的原因。而 language-ext 通过ad-hoc polymorphism绕过了这个限制可以创建一个非常像 Haskell trait 的数值类型并创建该数值 trait 的 ad-hoc 实例从而在不装箱的前提下实现泛型数值运算。这一点可以对照源码验证例如 TInt 实现了Numint其Add、Subtract、Multiply、Divide等方法全部直接操作int值类型没有任何接口类型的装箱开销public struct TInt : Numint, Boolint { [Pure] public static int Add(int x, int y) x y; [Pure] public static int Multiply(int x, int y) x * y; [Pure] public static int Divide(int x, int y) x / y; // ... }关键点在于这些成员都是static abstract的接口成员通过泛型类型参数而非运行时接口派发解析值类型自始至终以值类型形态参与运算。三、技术地基C# 的 static abstract 接口成员与 Trait 标记ad-hoc 多态在 C# 中能够成立依赖的是近年 C# 新增的static abstract接口成员。仓库的 Traits README 给出了最精简的演示public interface AddableSELF where SELF : AddableSELF { public static abstract SELF Add(SELF x, SELF y); }注意两点Add成员是static abstract接口带有SELF : AddableSELF的约束即 F-bounded polymorphism迫使类型继承以自身为参数的 trait。随后可以让两个完全不同的类型各自实现public record MyListA(A[] values) : AddableMyListA { public static MyListA Add(MyListA x, MyListA y) new (x.values.Append(y.values).ToArray()); } public record MyString(string value) : AddableMyString { public static MyString Add(MyString x, MyString y) new (x.value y.value); }这样同一个Add概念对MyList是拼接对MyString是字符串连接——且静态解析无虚调用开销。language-ext 把这个思路推向了「高阶类型 trait」higher-kinded traits以KF, A类型作为一切的锚点而Class Instances体系中的每个 trait 接口都带有[Trait(...)]标记见 TraitAttribute例如[Trait(Eq*)]→EqA[Trait(Ord*)]→OrdA[Trait(Num*)]→NumA[Trait(Bool*)]→BoolA[Trait(Hashable*)]→HashableA这些标记用于在已加载的程序集中解析/发现合适的实例是EqDefault等自动寻找实例机制的基础下文详述。四、trait 契约一览Eq、Ord、Hashable、Num、Bool、Monoid理解 class instances先要理解它们实现的 trait 接口。以下是仓库 LanguageExt.Core/Traits 中与本主题直接相关的核心契约Trait继承关系核心静态抽象成员源码位置EqAHashableA, TraitEquals(A x, A y)Eq.csOrdAEqACompare(A x, A y)返回 -1/0/1Ord.csHashableA—GetHashCode(A x)Hashable.Trait.csNumAOrdA, ArithmeticAAbs、Signum、FromInteger、FromDecimal、FromFloat、FromDouble、DivideNum.csArithmeticATraitAdd、Subtract、Multiply、NegateArithmetic.csBoolATraitTrue()、False()、And、Or、Not、XOr、Implies、BiConditionBool.csMonoidASemigroupAEmpty单位元Monoid.cs值得注意的几点继承设计Eq继承Hashable任何可判等的类型都必须同时能给出哈希码保证哈希容器HashMap、HashSet等的等值语义与判等语义一致Ord继承Eq可比较的类型自然可判等Num继承Ord与Arithmetic数值类型天然兼具可比较与可四则运算两种能力Num还额外要求Abs/Signum以及从int/decimal/float/double到A的转换函数FromInteger等Monoid继承Semigroup幺半群 半群 单位元Empty。Monoid接口还暴露了一个Instance静态虚属性把 trait 以记录值形式传递绕开 C# 约束系统的一些限制见 Monoid.cs。五、Class Instances 目录导览Eq / Ord / Hashable / Monoid / T 系列Class Instances 目录 按 trait 类别分子目录每个子目录下是按具体类型展开的实例集合Eq/EqArr、EqArray、EqBigInt、EqBool、EqChar、EqDateTime、EqDecimal、EqDefault、EqDouble、EqEither、EqFloat、EqGuid、EqHashSet、EqInt、EqIterable、EqLong、EqLst、EqMap、EqOption、EqQue、EqRecord、EqSeq、EqSet、EqShort、EqStck、EqString、EqTask、EqTrue、EqTuple2、EqTypeInfo、EqEdit、EqException、EqEnumerable、EqIdentity、EqPatch等——覆盖了从基本值类型、字符串到各类不可变集合与 ADTOption、Either、Lst、Seq、Map、Set、Arr、Que、Stck等的判等Ord/与Eq目录几乎一一对应的排序实例OrdInt、OrdString、OrdLst、OrdSeq、OrdTupleFirst等Hashable/HashableInt、HashableString及其 culture 变体、HashableTuple等哈希实例Monoid/Addition、Product、All、Any、MError、MUnit——从源码看这些实例当前以注释形式保留在仓库中反映了一个演进事实现代的MonoidA直接由 trait 自身的Empty属性 Semigroup.combine完成见 Semigroup.Module.cs不再需要独立的类实例类型T 系列目录根部TInt、TLong、TShort、TBigInt、TFloat、TDouble、TDecimal、TChar、TString、TBool、TBoolBool、TNumericChar——T代表 type class 的聚合实例一个类型把多个 trait 打包实现例如TInt : Numint, Boolint、TString : Ordstring、TBool : Ordbool, Boolbool。这种命名规律非常实用EqXxx 仅判等OrdXxx 判等 排序HashableXxx 仅哈希Txxx 该类型的全能类实例。六、实例源码剖析从 EqInt 到 TInt6.1 EqInt最小实例的样板EqInt 是理解整个体系的样板public struct EqInt : Eqint { [Pure] public static bool Equals(int a, int b) a b; [Pure] public static int GetHashCode(int x) HashableInt.GetHashCode(x); }要点用struct而非 class承载实例配合static成员零装箱、零堆分配因为EqA继承HashableA所以EqInt必须同时实现Equals与GetHashCodeGetHashCode直接委托给对应的哈希实例 HashableInt其实现就是x.GetHashCode()体现实例之间互相组合的复用模式。6.2 OrdInt判等 排序OrdInt 在EqInt基础上增加Compare返回值遵循约定x 大于 y 返回 1x 小于 y 返回 -1相等返回 0public struct OrdInt : Ordint { public static bool Equals(int x, int y) x y; public static int Compare(int x, int y) x.CompareTo(y); public static int GetHashCode(int x) x.GetHashCode(); }6.3 EqString 与它的文化变体家族字符串判等充满文化敏感性EqString.cs 中与EqString同文件定义了 6 个变体实例完整对应 .NETStringComparer的语义矩阵实例底层 StringComparer适用场景EqStringa b默认常规字符串判等EqStringInvariantCultureStringComparer.InvariantCulture跨环境稳定排序/判等EqStringInvariantCultureIgnoreCaseStringComparer.InvariantCultureIgnoreCase跨环境、忽略大小写EqStringOrdinalStringComparer.Ordinal性能敏感、逐字节比较EqStringOrdinalIgnoreCaseStringComparer.OrdinalIgnoreCase高性能忽略大小写如文件名、Key 比较EqStringCurrentCultureStringComparer.CurrentCulture跟随当前文化EqStringCurrentCultureIgnoreCaseStringComparer.CurrentCultureIgnoreCase跟随当前文化、忽略大小写每个变体的GetHashCode同步委托给对应的HashableStringXxx见 HashableString.cs确保判等语义与哈希语义永远一致——这是哈希容器正确性的前提。6.4 TInt聚合型数值类实例TInt 一次性实现Numint与Boolint两个 trait是类实例聚合的典范。它完整覆盖比较Equals、Compare、GetHashCode算术Add、Subtract、Multiply、Divide、Abs、Signum、Negate数值转换FromInteger(int)、FromDecimal(decimal)、FromFloat(float)、FromDouble(double)——统一了其他数值类型 →int的入口布尔运算位级True()返回-1、False()返回0C 语言风格的真值约定并实现And、Or|、Not~、XOr^、Implies蕴含、BiCondition双条件/同或。public static int Implies(int a, int b) And(a, True()) False() ? True() : b; public static int BiCondition(int a, int b) Not(XOr(a, b));Boolint的意义在于任何需要真/假值的抽象例如条件表达式、谓词组合都可以泛型化到数值类型上。同理 TBool 为bool提供OrdboolBoolboolTString 为string提供Ordstring并在同文件中以TStringOrdinal、TStringOrdinalIgnoreCase、TStringCurrentCulture、TStringCurrentCultureIgnoreCase等形式复刻了文化变体。七、如何调用Prelude 的 equals、compare 与自动解析实例class instances 的实际调用入口是 Prelude 的泛型辅助函数。它们接受实例类型作为第一个泛型参数例如 Eq.Prelude.cspublic static bool equalsEQ, A(A x, A y) where EQ : EqA EQ.Equals(x, y);以及 Ord.Prelude.cs[MethodImpl(MethodImplOptions.AggressiveInlining)] public static int compareORD, A(A x, A y) where ORD : OrdA ORD.Compare(x, y);典型用法using static LanguageExt.Prelude;// 用 EqInt 判等两个 int bool same equalsEqInt, int(1, 1); // true // 用 OrdStringOrdinalIgnoreCase 做忽略大小写的排序比较 int order compareOrdStringOrdinalIgnoreCase, string(abc, ABC); // 0 // 用 TInt 执行泛型数值操作 int sum TInt.Add(2, 3); // 5 int abs TInt.Abs(-7); // 7 // 用 EqStringInvariantCulture 做跨文化判等 bool eq equalsEqStringInvariantCulture, string(hello, hello); // true这些equals/compare还有大量针对容器类型的重载OptionA、EitherL,R、ValidationA,B、LstA、SeqA、SetA、ArrA、QueA、数组、IEnumerableA等让你可以为任意嵌套结构指定判等/排序实例详见 Eq.Prelude.cs 与 Ord.Prelude.cs。当你不确定该用哪个实例时可以用EqDefaultAEqDefault.cs它会在已加载程序集中解析合适的Eq实例找不到时回退到 .NET 标准的EqualityComparerA.Default并内置 null 处理与ReferenceEquals短路优化public static bool Equals(A a, A b) { if (a is null) return b is null; if (b is null) return false; if (ReferenceEquals(a, b)) return true; return EqResolveA.Equals(a, b); }同级还提供了双类型版本EqDefaultA, B当A、B底层类型相同时做结构化判等类型不同直接返回 false。在 Concurrency/VersionHashMap 等内部实现中EqDefaultK正是作为键判等实例被广泛使用的例如 VersionHashMap.ConflictV.K.V.cs 以EqDefaultK作为泛型键的Eq参数。八、设计价值与使用建议8.1 为什么值得理解这套体系语义显式化把EqualityComparerT.Default这种隐含默认变成类型层面的显式选择——equalsEqStringOrdinalIgnoreCase, ...一望即知比较策略性能可控struct static abstract 的组合消除了装箱与虚调用泛型代码对值类型同样高效MethodImplOptions.AggressiveInlining进一步鼓励内联可组合性实例之间互相委托EqInt.GetHashCode → HashableInt、TString.Compare → OrdString形成清晰的复用层次。8.2 实践建议从 T 系列入手需要某类型的完整能力时优先用TInt、TString、TBool这类聚合实例而不是分别拼凑EqXxxOrdXxx字符串注意文化涉及排序、唯一键、跨环境持久化时按 EqString 变体表 明确选择比较策略避免隐式默认带来的行为漂移自定义类型的实例化可仿照EqInt的样板为自己领域类型编写struct MyEq : EqMyType即可把领域类型无缝接入equals、compare、哈希容器等泛型设施默认兜底不确定解析能力时用EqDefaultA/OrdDefaultA保证回退到 BCL 语义。8.3 深入阅读路径想深入 ad-hoc 多态的原理可阅读 Class Instances README 中指向的 wiki 专题以及 Traits README 对static abstract技术路线的完整阐述想查看全部可用实例直接浏览 Class Instances 目录 下的Eq/、Ord/、Hashable/、Monoid/子目录与 T 系列文件想看实例在真实场景中的运用可查阅 HashMap、Set、VersionHashMap 等内部以EqA/OrdA为泛型参数的集合与并发结构以及 LanguageExt.Tests 的相应测试如HashMapTests.cs、SetTests.cs对判等/排序语义的验证。总结class instances 是 language-ext 把 Haskell 类型类思想落地到 C# 的关键一环——它用 struct 承载静态实现、用static abstract接口成员完成无装箱的泛型派发、用[Trait]标记支撑实例的自动发现最终让你获得同一个泛型算法任意指定语义的 ad-hoc 多态能力。赞分享后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载相关推荐如何3步实现网易云音乐离线下载完整Python命令行解决方案如何3步实现网易云音乐离线下载完整Python命令行解决方案 还在为网易云音乐无法永久保存喜爱的歌曲而烦恼吗netease cloud music dl是一CLIlegged_control与ros-controls无缝对接机器人硬件接口开发指南legged_control与ros controls无缝对接机器人硬件接口开发指南 legged_control是一个基于OCS2和ros controls机器人ROS免费开源小说阅读器终极指南5分钟快速上手Uncle小说PC版免费开源小说阅读器终极指南5分钟快速上手Uncle小说PC版 还在为找不到心仪小说而烦恼吗Uncle小说PC版是你的一站式解决方案这款免费开源的小说下载阅桌面应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站