Haskell构造器类为何不支持对内容类型的约束?
构造器类出现前的Haskell类型类限制
在M.P.Jones 1993年提出构造器类之前,Haskell的class声明有严格限制:只能包含单个参数,且该参数的kind必须是Type(当时记为*)。构造器类的核心创新在于允许参数的kind为Type->Type(即*->*),但这也带来了一个明显的局限:实例声明无法对容器的“内容”类型添加约束,比如没办法要求Set的元素必须属于Ord类型。
尝试为带约束的容器定义类
我们可以思考为什么不能用下面这种类声明形式:
class Functorb (f b) where -- 目前class声明不允许这种写法 fmapb :: (a -> b) -> f a -> f b instance (Ord b) => Functorb (Set b) where fmapb f NilSet = NilSet fmapb f xs = fromList (map f $ toList xs)
这个写法里fmapb的签名和Haskell标准的fmap完全一致,而且不需要用到FlexibleInstances这类后续扩展。另外输入Set的内容类型a不需要Ord约束,因为我们只需要消费这个类型的元素,而不需要构建它的实例。
多参数类型类与类型等式的尝试
1993年的时候,多参数类型类(MultiParamTypeClass)还只是处于考量阶段,并未实现,当时的论文指出这个特性会因为类型歧义问题而无法工作。但在GHC 8.10版本中,按照评论建议实现的组合操作并没有出现歧义,将其应用在Data.Set Int上也能得到预期结果。
另外,使用类型等式(~)的实现也可以正常运行:
class Fmapb fb where fmapb :: fb ~ f b => (a -> b) -> f a -> f b instance Ord b => Fmapb (Set b) where fmapb f xs = fromList $ Prelude.map f (toList xs)
类型族的实现方式
现在我们还可以通过类型族(type family)实现类似的功能,写法相对简洁:
type family Constr (fb :: Type) :: (Type -> Type) where Constr (f b) = f type family CArg (fb :: Type) :: Type where CArg (f b) = b class Functorb fb where fmapb :: (a -> CArg fb) -> ((Constr fb) a) -> fb
相关库支持
还有一个constrained-monads库,它通过-XRebindableSyntax扩展支持在do块中使用带约束的monad操作。
内容的提问来源于stack exchange,提问作者AntC
相关产品推荐
相关产品推荐

