Haskell中两类同构约束定义方式的实际差异探究:带平凡实例的类约束 vs 约束同义词
咱们来拆解一下你提出的这两个Haskell约束定义的差异,还有那个量化约束报错的问题:
一、两种定义的实际差异(除量化能力外)
这两种写法表面上看起来都是把From a b和To a b打包成一个约束,但实际使用起来有几个关键区别:
自定义实例的可能性:
方式一的类型类定义,哪怕现在只写了那个“平凡”的实例,后续你完全可以给特定的a和b写更具体的实例——比如针对某种特殊类型对,你可以绕过From和To的约束,直接实现IsomorphismFromTo(当然要注意Haskell的实例重叠规则)。而方式二的约束同义词就是个单纯的别名,永远等价于那两个约束的组合,没有任何自定义实例的空间。附加能力的扩展性:
类型类可以加默认方法或者关联类型家族。比如你可以给IsomorphismFromTo加个默认的同构转换方法:class (From a b, To a b) => IsomorphismFromTo a b where iso :: a -> b iso = from -- 直接复用From的from方法 invIso :: b -> a invIso = to -- 直接复用To的to方法 instance (From a b, To a b) => IsomorphismFromTo a b约束同义词就做不到这一点,它只是约束的打包,没有任何附加的功能。
错误提示的友好度:
当约束不满足时,类型类的错误信息会直接提到IsomorphismFromTo a b,而约束同义词会展开成(From a b, To a b)。如果你的约束组合很复杂,类型类的错误提示会更聚焦,更容易定位问题。
二、量化约束报错的原因
你遇到的那个"You can't specify an instance for a tuple constraint"错误,本质是约束同义词和类型类在处理高阶量化时的本质区别:
约束同义词
type IsomorphismFromTo a b = (From a b, To a b)本质是个约束别名,展开后是个元组约束(多个约束的组合)。GHC目前不支持把这种元组约束直接放到forall量化里去定义高阶约束——它没法把“对所有x,同时满足From和To约束”当成一个整体的高阶约束来处理。而类型类
IsomorphismFromTo a b是个原子约束,哪怕它内部包含了两个子约束,GHC会把它当成单个约束单元。所以forall x. IsomorphismFromTo (f x) (g x)是合法的——它表示“对所有x,(f x)和(g x)满足这个原子约束”,GHC可以正确处理这种量化的原子约束。
说白了,类型类能把多个约束打包成一个可以被高阶量化的“原子块”,而约束同义词只是单纯的展开,做不到这一点。
总结一下
如果你的需求只是临时打包两个约束,不需要扩展方法、自定义实例,也不用高阶量化,那约束同义词更简洁;但如果需要高阶量化、未来可能要加实例或方法,类型类的方式灵活度和扩展性都强得多。
内容的提问来源于stack exchange,提问作者nicolas

