1

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

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

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

如果f两者兼而有之FunctorContravariant那么当您考虑到每个类的定律时,它实际上就不能以任何有意义的能力使用它的论点。这种方法非常有用。fmap f ≡ phantom在这两种情况都存在且合法的情况下,我们有以下法律:contramap f ≡ phantom

既然fmap f ≡ contramap f ≡ phantom,为什么我们需要ContravariantFunctor实例?以另一种方式做这件事不是更方便:为一个类创建一个实例Phantom,它引入了phantom方法,然后自动推导出Functor和的实例Contravariant?

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

在实现and的实例时,我们将让程序员不必重写它phantom两次(以实现fmapand contramapconst phantom如注释中所述)。我们将允许编写一个实例而不是两个!此外,对我来说,为所有 4 种差异情况都有类似乎很好而且很惯用:, , (然而,有些人建议使用interface 而不是) 和.ContravariantFunctorFunctorContravariantInvariantProfunctorInvariantPhantom

另外,这不是更有效的方法吗?() <$ x $< ()需要两次遍历(就像我们可以遍历一个幻象函子一样......),只要程序员可以更快地执行这个转换。据我了解,当前phantom方法不能被覆盖。

那么,为什么库开发者不选择这种方式呢?目前的设计和我所说的设计的优缺点是什么?

4

3 回答 3

6

有许多类型是 Functor 的实例而不是 Phantom 的实例,同样是逆变的。对于此类类型,由于实例重叠,您提出的结构将是一个大问题。

instance Phantom f => Functor f

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

于 2021-07-22T20:56:48.010 回答
2

为了避免 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 实例。

于 2021-07-22T21:42:50.710 回答
0

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

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

问题是人们可能不会对花时间实例化类感兴趣。

于 2021-07-22T21:17:49.633 回答