Haskell为何不单独设计Phantom类替代Functor与Contravariant双实现?
核心限制:你提出的默认实例方案不可行
你划掉的那两个全局实例在Haskell的类型类体系下根本无法实际落地,原因有二:
- 实例匹配规则限制:GHC的类型类实例匹配只看实例头,不考虑上下文约束。你写的
instance Phantom f => Functor f中,实例头是Functor f,会匹配所有的类型构造器f,和所有已存在的Functor实例(比如[]、Maybe、IO等)直接冲突,编译器会直接报实例重复错误。 - 孤儿实例问题:这种实例既没有定义在类型类所在的模块,也没有定义在类型构造器所在的模块,属于孤儿实例,会导致非常严重的实例一致性问题,是Haskell生态中强烈不推荐的写法,正式的公共库绝对不会提供这种实例。
现有设计的考量
contravariant库选择当前设计主要是出于兼容性和生态惯性的考虑:
Functor是base库中最基础的核心类型类之一,生态中已经有无数代码依赖它的定义,不可能为了加入Phantom类型类修改基础库的已有设计,成本太高。- 当前设计不需要侵入任何已有类型类的定义,只要你的类型同时实现了合法的
Functor和Contravariant实例,就可以直接使用phantom函数,兼容性极强,几乎没有接入成本。 - 对于绝大多数简单的幻影类型(比如
Const a),现有默认实现() <$ x $< ()会被GHC的优化器完全优化掉,并不会产生额外的两次遍历开销,性能问题在大多数场景下不存在。
两种设计的优缺点对比
| 设计方案 | 优点 | 缺点 |
|---|---|---|
| 现有contravariant库设计 | 1. 完全兼容已有生态,无侵入性 2. 不需要额外的类型类依赖,使用门槛低 3. 符合现有方差类型类的分层设计 | 1. 自定义幻影类型需要重复实现fmap和contramap的冗余逻辑2. 复杂幻影类型的默认 phantom实现可能有不必要的遍历开销 |
你提出的Phantom类型类设计 | 1. 减少重复代码,自定义幻影类型只需要实现一次phantom即可2. 支持手动重写高效的 phantom实现,避免冗余遍历3. 四种方差(协变、逆变、不变、幻影)的类型类定义更完整统一 | 1. 无法提供全局的自动Functor/Contravariant实例,用户仍然需要手动给自己的类型写两个实例的绑定,只能减少实例实现的代码量,不能完全省略两个实例的声明2. 需要额外引入新的类型类,增加了依赖和学习成本 3. 无法和生态中已有的大量只依赖 Functor/Contravariant的代码无缝兼容 |
补充说明
实际上你提出的Phantom类型类并非没有被实现,第三方的相关库中已经提供了类似的PhantomFunctor类型类定义,只是因为上述的兼容性问题,没有成为主流的标准实现。如果你自己的项目中需要大量定义幻影类型,完全可以在自己的代码中定义这个类型类,给自己的类型写对应的实例,避免重复代码。
内容的提问来源于stack exchange,提问作者Zhiltsoff Igor
相关产品推荐
相关产品推荐

