为什么不是扩展 Functor Contravariant 的 Phantom class?

Why not a Phantom class which extends Functor Contravariant?

我正在玩弄 Data.Functor.Contravariant. The phantom 方法引起了我的注意:

phantom :: (Functor f, Contravariant f) => f a -> f b
phantom x = () <$ x $< ()

或者,更具体地说,注释:

If f is both Functor and Contravariant then by the time you factor in the laws of each of those classes, it can't actually use it's argument in any meaningful capacity. This method is surprisingly useful. Where both instances exist and are lawful we have the following laws: fmap f ≡ phantom, contramap f ≡ phantom

既然 fmap f ≡ contramap f ≡ phantom,为什么我们需要 ContravariantFunctor 个实例?换一种方式做这件事不是更方便:为一个 class Phantom 创建一个实例,它引入了 phantom 方法, 然后 FunctorContravariant?

自动派生个实例
class Phantom f where
    phantom :: f a -> f b
instance Phantom f => Functor f where
    fmap _f = phantom

instance Phantom f => Contravariant f where
    contramap _f = phantom

我们将使程序员不必重写此 phantom 两次(以实现 fmapcontramap,它们是 const phantom,如注释中所述) 在实现 ContravariantFunctor 的实例时。我们将允许编写一个实例而不是两个!此外,对我来说,对所有 4 种方差情况都有 classes 似乎很好,也很惯用:FunctorContravariant, Invariant (yet, some suggest using Profunctor interface instead of Invariant) 和 Phantom.

此外,这不是更高效的方法吗? () <$ x $< () 需要两次遍历(尽可能多地遍历幻函数...),只要程序员可以更快地执行此转换即可。据我了解,当前的 phantom 方法无法被覆盖。

那么,为什么库开发者不选择这种方式呢?现在的设计和我说的设计有什么优缺点?

有很多类型是 Functor 的实例而不是 Phantom 的实例,同样也是 Contravariant。对于此类类型,您提出的结构将是一个大问题,因为实例重叠。

instance Phantom f => Functor f

并不意味着“如果 f 是一个 Phantom 那么它也是一个 Functor”。在类型类解析期间只搜索实例头,约束稍后出现。这与open world assumption有关。因此,您正在为 f 声明一个 Functor 实例,这是一个完全不受约束的类型变量,它将与所有其他可能的实例声明重叠。

你真正能做的最好的事情是这样的:

class (Functor f, Contravariant f) => Phantom f where
  phantom :: f a -> f b
  phantom x = () <$ x $< ()

问题是人们可能不会有兴趣花时间实例化 class。

为了避免 amalloy 提到的重叠实例,您可以定义一个可以与 DerivingVia 一起使用的新类型:

{-# LANGUAGE DerivingVia #-}

import Data.Functor.Contravariant hiding (phantom)

class (Functor f, Contravariant f) => Phantom f where
  phantom :: f a -> f b

newtype WrappedPhantom f a = WrappedPhantom (f a)

instance Phantom f => Phantom (WrappedPhantom f) where
  phantom (WrappedPhantom x) = WrappedPhantom (phantom x)

instance Phantom f => Functor (WrappedPhantom f) where
  fmap _ = phantom

instance Phantom f => Contravariant (WrappedPhantom f) where
  contramap _ = phantom

-- example of usage:

data SomePhantom a = SomePhantom
  deriving (Functor, Contravariant) via WrappedPhantom SomePhantom

instance Phantom SomePhantom where
  phantom SomePhantom = SomePhantom

它不像自动拥有实例那么方便,但它仍然意味着您不必手动实现 Functor 和 Contravariant 实例。