位置:首页 > Scala > 什么是反变函子:Contravariant Functor 的直觉解释

什么是反变函子:Contravariant Functor 的直觉解释

时间:2026-08-21  |  作者:宇宙开黑者  |  阅读:0

contra variant

contra variant是一个定义了contramap函数的functor

Contra variant Functor 的直觉解释

def contramap[A, B](fa: F[A])(f: B => A): F[B]

可以发现,这跟普通的map很像。

区别在于,f函数的方向相反

def map[A, B](fa: F[A])(f: A => B): F[B]

因此叫contramap。

平时所说的定义了map的functor,可以叫covariant functor。定义了contramap的是contra variant functor。

contra variant的例子有Show和scala.math.Ordering。

contramap的intuition

我们都知道,map可以把函数f应用到一个容器里面。这是map的intuition。

那么,contramap的intuition是什么?

先看contramap的参数。

  • F[A]:这是一个容器里面装着A
  • B => A:这是一个把B转换成A的函数

很明显,不能把这个函数直接应用进去,因为容器装的不是B。

所以,这里先忘掉map intuition中的容器。

换一个角度看,contramap会非常直观。

有一天,一位数学家感觉自己对数学已经厌倦到了极点,于是他跑去消防队,宣称自己想要成为一名消防员。消防队长说:“您看起来很不错,不过我得先给您做个测试。”消防队长带着数学家来到消防队后院的小巷,巷子里有一个货栈、一只消防栓和一卷软管。消防队长问道:“假如货栈着火了,您会怎么做?”数学家回答:“我会把消防栓接到软管上,打开水龙头,把火扑灭。”消防队长说:“完全正确!最后一个问题:假如您走进小巷,而货栈没有着火,您会怎么做?”数学家疑惑地思考了很久,最后回答道:“那我就把货栈点燃。”消防队长大惊失色:“什么?太可怕了!您为什么要把货栈点燃?”数学家回答:“这样我就把问题简化成了一个我已经解决过的问题。”

这就是contramap的核心直觉:把一个新问题,化简成一个已经解决过的问题。

Show例子

再看一个Show的例子。

case class Money(amount: Int)
case class Salary(size: Money)

implicit val showMoney: Show[Money] = Show.show(m => s"$$${m.amount}")
implicit val showSalary: Show[Salary] = showMoney.contramap(_.size)

上面两个例子中的共同点,都是把一个问题化简为一个已经解决过的问题。

当已经定义了Show[Money]之后,再定义Show[Salary]时,就可以通过Salary.size方法把Salary转换成Money。

这样,问题就自动解决了。

contramap的应用

scala.math.Ordering中已经有contramap了。

def by[T, S](f: T => S)(implicit ord: Ordering[S]): Ordering[T]

这里的by,其实就是contramap。

  • 要求解一个未知的问题:Ordering[T]
  • 需要一个已解决的问题:Ordering[S]
  • 还需要一个转换的方法:T => S

Subtyping和contra variant的关系

相信大家都了解里氏替换原则吧。

简单来说,如果一个函数所需要的参数类型是A,那么A的所有子类型B都能够作为参数传入该函数。

比如说,有一个函数是getAge(human: Human),那么我们既可以传入getAge(man),也可以传入getAge(woman)。

Contra variant有这样的一个属性:

如果有一个contra variant Functor F,并且B <: A,那么F[A] <: F[B]。

背后的intuition

A可以看作是全人类,B可以看作是某一类人。

如果有一个问题,需要关于B这一类人的解决方案。那么如果我有关于A的解决方案,我一样可以套用进去。

again,F[B]可以看成是关于类型B的解决方案。

  • Show[B]是解决如何print B
  • Ordering[B]是如何对B排序

实现方法

实现方法非常简单。

class A
class B extends A

val showA: Show[A] = Show.show(a => "a!")
val showB: Show[B] = showA.contramap(identity[A])

一般情况下,在contramap需要提供如何从B转换成A的方法。

但是在这里,B是A的subtype,所以根本就不需要转化。

只需要告诉编译器,我就是一个A。

看回人类的那个例子。

例如我需要“测量男人年龄”的方案,然后我已经有一个“测量人类年龄”的方案,我只需要说“男人也是人类”,这个问题就自动解决了。

因此F[A] <: F[B],所有的F[B]可以用F[A]代替。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多