【问题标题】:What is the purpose of contramap?反映射的目的是什么?
【发布时间】:2018-02-02 16:52:33
【问题描述】:

我阅读了很多关于 contramap 的文章,发现 https://hackage.haskell.org/package/contravariant-1.4.1/docs/Data-Functor-Contravariant.html#g:3 是最好的。

反正我找到了,例如如何使用:

*Lib Data.Functor.Contravariant> a = Predicate (\x -> x > 20)
*Lib Data.Functor.Contravariant> :t contramap
contramap :: Contravariant f => (a -> b) -> f b -> f a
*Lib Data.Functor.Contravariant> :t contramap (\x -> x * 2)
contramap (\x -> x * 2) :: (Num b, Contravariant f) => f b -> f b
*Lib Data.Functor.Contravariant> :t contramap (\x -> x * 2) a
contramap (\x -> x * 2) a :: (Ord b, Num b) => Predicate b
*Lib Data.Functor.Contravariant> x = contramap (\x -> x * 2) a
*Lib Data.Functor.Contravariant> getPredicate x 45
True 

但不知道它在哪里有用。

在我上面发布的网站上,它说:

而在 Haskell 中,可以将 Functor 视为包含或 产生值,逆变函子是一个函子,它可以 被认为是消费价值。

看函子的定义:

class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b

它使用a 类型的值并产生b 类型的值。在contramap 上,它会消耗该值。

class Contravariant (f :: * -> *) where
  contramap :: (a -> b) -> f b -> f a

它使用ab 哪个变量类型?

还有来自https://www.fpcomplete.com/blog/2016/11/covariance-contravariance 的关于正面和负面立场的问题。网站上写着:

  • 正位:类型变量是 函数的结果/输出/范围/余域
  • 负位置:类型变量是参数/输入/域 函数

查看 contramap 类型定义:

contramap :: (a -> b) -> f b -> f a

作者指的是哪个类型的变量?

【问题讨论】:

  • 你误解了引用部分的意思;但它也不是很清楚的语言,你应该避免阅读任何以“可以想到......”开头的解释。也就是说,这意味着“一个 Functor”(对于一些 FFunctor F 和一些 A 的值 x :: F A)可以被认为是产生 A 类型值的计算;并且逆变函子可以被认为是消耗A类型的值。

标签: haskell


【解决方案1】:

函子f 被认为是“包含”或“产生”值,因为f a 就像a 值的容器。 fmap 允许您转换 f 持有的值。
示例:

  • [a]'包含'非负数的a 类型值
  • IO a 可能会执行一些 IO 并“返回”或“产生”a 类型的值
  • (->) r a“包含”a 类型的值,每个值 r

现在Contravariant f 可以“吸收”或“消耗”值。 contramap 允许您在 f a 使用之前转换它们。
主要的例子通常是使用类似
newtype Op r a = Op { runOp :: a -> r }
(注意您使用的 Predicate 似乎只是 Op Bool
现在我们有了可以“消耗”a 类型值的东西。(类比可能对Op (IO ()) 更有意义)
继续这个值“消耗”的例子,考虑o = Op (\x -> putStrLn x) :: Op (IO ()) String,现在如果我们想使用o,但是对于Show a => a类型的值呢?这就是contramap 的用途!
contramap show o :: Show a => Op (IO ()) a
(注意在这个简单的例子中runOp (contramap show o) 只是print

编辑:
关于Contravariant 的另一件有趣的事情是关于编写它。
给定Contravariant c, Contravariant d, Functor f
newtype Compose f g a = Compose { runCompose :: f (g a) }
我们有:

  • Compose f c 也是 Contravariant
    contramap f (Compose fca) = Compose $ fmap (contramap f) fca
  • Compose c f 也是 Contravariant
    contramap f (Compose cfa) = Compose $ contramap (fmap f) cfa
  • Compose c d 实际上是一个Functor
    fmap f (Compose cda) = Compose $ contramap (contramap f) cda

【讨论】:

  • 数据类型的定义必须像 Op { runOp :: a -> r } 而不是例如 contramap on Maybe a 没有意义,因为它会产生一个值,对吧?
  • Maybe a 可以被认为是长度为 0 或 1 的列表,即它“包含”0 或 1 个 a 类型的值。你不能为Maybe 定义contramap,因为如果你这样做了,你就会有phantom = contramap (const ()) . fmap (const ()) :: (Functor f, Contravariant f) => f a -> f b,从那以后我们可以使用fromJust . phantom . Just 将任何a 转换为任何b。例如Functor 也是Contravariant,有newtype Const c a = Const { getConst :: c },其中fmap f = contramap f = Const . getConst
【解决方案2】:

看函子的定义:

class Functor (f :: * -> *) where
    fmap :: (a -> b) -> f a -> f b

它使用a 类型的值,并产生b 类型的值。

这并不准确,是您困惑的根源。

具体来说,fmap 类型中存在三个不同的对象:函数a -> b、“函数”值f a 和“函数”值f b,以及a 是否为消费或生产因这些对象而异,b 是消费还是生产也是如此。

  • a -> b 类型的函数使用 a 并生成 b。 (这是你说的那句话,但增加了一个新的内容,指定 它适用于哪个对象。)
  • 如果fFunctor,则f a 类型的值“产生”as。
  • 如果fFunctor,则f b 类型的值“产生”bs。

我们可以将这些观察结果扩展到更大的函数类型。

  • 如果fFunctor,那么f a -> f b类型的函数消耗f as并产生f bs;由于函子是正数,这反过来意味着该函数消耗as 并产生bs。
  • 如果fFunctor,那么(a -> b) -> f a -> f b类型的函数使用a -> b——也就是说,在实现中,我们将产生as提供给a -> b函数并消耗它返回的bs!它这样做是为了产生一个f a -> f b,它消耗as 并产生bs。

注意在上面的描述中,“消费”和“生产”的角色交替出现了很多,即使在称为fmap 的单个对象中也是如此;所以说“fmap 消耗 as 并产生 bs”并不能说明全部。

以下陈述也以类似的方式不精确:

在 Haskell 中,可以将 Functor 视为包含或产生值,而逆变函子是可以视为消费值的函子。

您已将此解释为意味着 fmap 作为(函数)对象包含值,而 contramap 产生值。但这不是预期的;相反,该声明旨在针对(逆变)函数值本身,而不是应用于它们的(反)映射。更准确的说法是:

而在 Haskell 中,可以将 f a 类型的值(fFunctor)视为包含或产生 a 值,逆变函子 f 产生的值输入 f a 可以认为是使用 a 值。

关于正负仓位的问题,可以关注some of my previous discussion of this topic here on SO

【讨论】:

    猜你喜欢
    • 2021-12-01
    • 2010-09-15
    • 2013-05-11
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多