Scala Cats - Functor

Functor

Functor 是用来表示抽象化出可以调用 map 方法的结构 的 类型类. 这种结构比如说有 List, Option, 和 Future.

实现了 Functor 的类型, 可以看成是某种容器 或者是上下文, 而 map 方法就是表示要把容器中的东西变换成另外一种东西. map 方法的参数是 一个函数, 用来将 容器中的 A 类型转换成 B类型. 所以对于要实现 Functor 的类来说, 必须至少拥有一个类型参数, 来作为其内部的类型.

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

// Example implementation for Option
implicit val functorForOption: Functor[Option] = new Functor[Option] {
  def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match {
    case None    => None
    case Some(a) => Some(f(a))
  }
}

Functor 实例必须遵循两个规则:

  • 结合律:

    • fa.map(f).map(g) = fa.map(f.andThen(g))

    类似于函数里面的 f(g(x)) = g(f(x))

  • 同一律:

    • fa.map(x => x) = fa

    x => x, 也就是直接返回参数的函数称为 id (单位函数)

不同的角度

另一种看待 Functor[F] 的角度是, F允许从一个函数 A => B 提升(lift) 为一个新的函数 F[A] => F[B], 可以看看 lift 的函数签名:

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]

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

lift 方法看起来有点抽象, 把函数签名提取出来, 就是 我们已知某个上下文/容器 F, 还有 A => B 的函数, 然后我们就能够组合出 F[A] => F[B] 的函数

用 Functor 来管理效果(effect)

Functor 中的 F 常常被称为 “效果” 或者 “计算上下文”. 不同的效果将抽象出不同的行为, 尽管我们都是调用 map. 比如, Option 的效果就是将潜在的缺失值给抽象出来, 当对一个 Some (Option 的 子类) 调用 map 的时候, 参数中的函数会生效, 但是对于 None (Option 的 另一个子类) 的情况来说, 就会直接跳过.

使用这种观点, 我们可以把 Functor 视为具有拥有处理单个效果的能力 - 我们可以把一个纯函数应用到某个值上面而无需将其剥离其上下文.

举个例子, 对于 List(1,2,3), 要变成 List(2,4,6). 普通 Java 的做法是新建一个 List, 然后遍历1 2 3, 分别 * 2 然后放到新的 List 中. 在 Scala 中 可以使用 List(1,2,3).map( i => i * 2) 来实现, 这样我们就无需将元素从List拿出来, 再放回去这样操作. 当然 Java 以后也支持 stream 上的 map 操作, 一样道理.

Functor 的组合

如果你曾经使用过类似于 Option[List[A]] 或者 List[Either[String, Future[A]]] 的结构并尝试调用其 map, 就会遇到 类似于 _.map(_.map(_.map(f))) 这样的窘境. 这就引申出了, Functor 的组合, 当 FG 都拥有 Functor 的实例, 则 F[G[_]] 也拥有 Funtor 的实例.

这种组合可以通过 Functor#compose 来获得

也就是说 我们有两个类型F,G , 都拥有 Functor 的实例, 那么通过 Functor#compose 方法, 我们可以将其组合起来以获取 F[G[_]] 的 Functor 实例, 这样我们就不用重新写一个实现了.

import cats.Functor
import cats.implicits._

val listOption = List(Some(1), None, Some(2))
// listOption: List[Option[Int]] = List(Some(1), None, Some(2))

// Through Functor#compose
Functor[List].compose[Option].map(listOption)(_ + 1)
// res1: List[Option[Int]] = List(Some(2), None, Some(3))

这种方法允许我们使用 Functor 组合而不需要层层调用map, 但是在复杂的用例下可能会引入复杂性. 比如我们想调用一个依赖某个 Functor 类型的函数, 并且我们想要使用 组合的Functor 作为参数, 我们就不得不显示地传递 组合的实例 在函数调用的时候或者创建一个本地的隐式变量.

def needsFunctor[F[_]: Functor, A](fa: F[A]): F[Unit] = Functor[F].map(fa)(_ => ())

def foo: List[Option[Unit]] = {
  val listOptionFunctor = Functor[List].compose[Option]
  type ListOption[A] = List[Option[A]]
  needsFunctor[ListOption, Int](listOption)(listOptionFunctor)
}

这种情况下, 我们就可以使用 Nested 数据类型

import cats.data.Nested
import cats.implicits._

val nested: Nested[List, Option, Int] = Nested(listOption)
// nested: Nested[List, Option, Int] = Nested(List(Some(1), None, Some(2)))

nested.map(_ + 1)
// res2: Nested[List, Option, Int] = Nested(List(Some(2), None, Some(3)))

使用 Nested 方式, 另一个不同类型的组合类, 以通常的方式来解决可能的 SI-2712 问题 (也可以通过 部分一致化(partial unification) 来解决) , 但是具有语法和运行时封装和解封装的开销.

官网的整个解释还是挺抽象的, 入门还是建议去看看那本 《Cats with Scala》. 再搭配 《Haskell趣学指南》 食用更佳

参考

官方文档 https://typelevel.org/cats/typeclasses/functor.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值