【问题标题】:Why not a Phantom class which extends Functor Contravariant?为什么不是扩展 Functor 逆变的 Phantom 类?
【发布时间】:2021-09-30 02:29:57
【问题描述】:

我在玩Data.Functor.Contravariantphantom 方法引起了我的注意:

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

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

如果f 既是Functor 又是Contravariant,那么当你考虑到每个类的规律时,它实际上不能以任何有意义的能力使用它的论点。 这种方法非常有用。在这两种情况都存在且合法的情况下,我们有以下法律:fmap f ≡ phantomcontramap 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

在实现Contravariant 和@ 的实例时,我们将免除程序员重写此phantom 两次(以实现fmapcontramap,即const phantom,如注释中所述)的必要性987654350@。我们将允许编写一个实例而不是两个!此外,对我来说,为所有 4 种差异情况提供类似乎很好且惯用:FunctorContravariantInvariant(然而,some 建议使用Profunctor 接口而不是Invariant),以及Phantom.

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

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

【问题讨论】:

  • phantom 不是FunctorContravariant 的方法。您不需要“重写两次”甚至一次。
  • @FyodorSoikin 你这样做,实现fmapcontramap,它们是const phantom。抱歉措辞不好
  • 嗯,有,比如... ProxyConst 和... 还有什么,究竟是什么? (实际上,每种幻像类型都是Const 伪装的。)您可能会节省五秒钟的程序员时间,就像每十年一次一样。感觉很难为此激动。

标签: haskell covariance functor contravariance library-design


【解决方案1】:

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

instance Phantom f => Functor f

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

【讨论】:

  • 呵呵,这个效果很有意思。我不知道,谢谢。无论如何,即使我们放弃这些实例,有机会以自己的方式实现phantom 不是更方便吗?惯用地说,幻变是与协变和逆变分开的情况
  • 你会如何想象这样做?你会写class (Functor f, Contravariant f) =&gt; Phantom f where ...吗?这样做有什么意义?
  • 定义 Phantom 就像我们定义其余部分一样 - 没有任何扩展。 IE。 class Phantom f where phantom :: f a -&gt; f b。尽管实现背后的想法很酷,但我看不出有什么意义:(坚持默认的&lt;$$&lt;)我们同时运行fmapcontramap,它们与const phantom 相同。 IE。要运行phantom,我们通过两个独立函数运行const phantom 两次
  • @ZhiltsoffIgor 问题在于,如果该类型的作者已经编写了 Phantom 实例,那么您只能在该类型上使用 phantom。而作者通常不会。无论如何,对于不声明实例的作者,您最终都会想要独立的 phantom 函数。使用类版本会稍微高效一些,但不太通用。
  • @ZhiltsoffIgor 适用于任何Traversablefmap 的默认值比适用于任何Phantomfmap 的默认值更通用,因为可以将任何Phantom 设为@ 987654343@.
【解决方案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 和逆变器实例。

【讨论】:

  • 非常好,但FunctorContravariant 肯定应该是Phantom 的超类!
  • @dfeuer 你是对的,我认为添加的内容仍然适用。
  • Daniel Wagner 指出它甚至可以使用 Traversable: traverse _ = pure . phantom
【解决方案3】:

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

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

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

【讨论】:

  • 抱歉,我现在无法编辑问题。现在,你能回答我在@amalloy 的帖子下关于这个默认实现的有用性的问题吗?我稍后会添加到帖子中
  • @ZhiltsoffIgor,我真的不明白你在用const phantom 做什么;也许 amalloy 可以回答你的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-29
  • 1970-01-01
  • 2018-12-24
相关资源
最近更新 更多