我在玩Data.Functor.Contravariant
。phantom
方法吸引了我的眼球:
phantom :: (Functor f, Contravariant f) => f a -> f b
phantom x = () <$ x $< ()
或者,更具体地说,它的注释:
如果
f
既是Functor
又是Contravariant
,那么当你考虑到这些类中每一个的定律时,它实际上无法以任何有意义的方式使用它的参数。这种方法非常有用。在这两种情况都存在且合法的情况下,我们有以下法律:fmap f ≡ phantom
、contramap f ≡ phantom
既然是fmap f ≡ contramap f ≡ phantom
,为什么我们需要Contravariant
和Functor
实例?用另一种方式做这件事不是更方便吗:为一个类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
在实现Contravariant
和Functor
的实例时,我们将免除程序员重写两次phantom
的必要性(以实现fmap
和contramap
,它们是const phantom
,如注释中所述(。我们将允许编写一个实例,而不是两个!此外,对我来说,为所有4种方差情况都设置类似乎很好,也很习惯:Functor
、Contravariant
、Invariant
(然而,有些人建议使用Profunctor
接口而不是Invariant
(和Phantom
。
此外,这不是一种更有效的方法吗?() <$ x $< ()
需要两次遍历(就像我们可以遍历一个幻影函子一样…(,只要程序员可以更快地执行此转换。据我所知,当前的phantom
方法不能被覆盖。
那么,库开发人员为什么不选择这种方式呢?目前的设计和我所说的设计有什么优点和缺点?
有许多类型是Functor的实例,但不是Phantom的实例,同样也是Contravariant的实例。对于此类类型,由于实例重叠,您提出的结构将是一个大问题。
instance Phantom f => Functor f
并不意味着";如果CCD_ 29是幻影,那么它也是Functor";。在类型类解析过程中,只搜索实例头,稍后会出现约束。这与开放世界的假设有关。因此,您正在为f
声明一个Functor实例,这是一个完全不受约束的类型变量,它将与其他所有可能的实例声明重叠。
为了避免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实例。
你真正能做的最好的事情是这样的:
class (Functor f, Contravariant f) => Phantom f where
phantom :: f a -> f b
phantom x = () <$ x $< ()
问题是,人们可能不会对花时间实例化类感兴趣。