关于Scala:简化嵌套函子变换

说我有2个函子f0f1,我有一些看起来像这样的代码-

1
f0.map(v0 => f1.map(v1 => f0f1(v0, v1)))

有没有一种方法可以简化此过程,以便可以使用for表达式并使代码更简洁-

1
2
3
4
for {
  v0 <- f0
  v1 <- f1
} yield f0f1(v0, v1)

map功能可通过如下形式的语法糖使用:-

1
2
3
4
  implicit class FunctorOps[F[_], A](fa: F[A]) {
    def F = implicitly[Functor[F]]
    def map[B](ab: A => B): F[B] = F.map(fa)(ab)
  }


假设f0: F0[X]f1: F1[Y](其中F0F1都具有Functor实例)和f: (X, Y) => Z,则for理解等效于

1
f0.map(x => f1.map(y => f(x, y)))

将是

1
for (x <- f0) yield for (y <- f1) yield f(x, y)

示例:

1
2
3
val f0 = Option(42)
val f1 = List(1, 2, 3)
for (x <- f0) yield for (y <- f1) yield x * y

产生:

1
res1: Option[List[Int]] = Some(List(42, 84, 126))

1
for (y <- f1) yield for (x <- f0) yield x * y

产生

1
res2: List[Option[Int]] = List(Some(42), Some(84), Some(126))

在这种情况下,我不确定for的理解是否比嵌套的map更清晰。


我认为原则上您不可能追求的目标。

您追求的漂亮的for语法会转换为类似

1
f0.flatMap(v0 => (f1.map(v1 => f0f1(v0, v1))))

这里的问题是flatMap部分。要有一个flatMap,您需要一个
Monad,不仅是Functor,或者至少是flatMap
实例。但是与Functor相比,Monad不组成。所以没有
F[_]G[_]自动获取嵌套的Monad的方法,即使两者
它们是Monad。 [1]

使用某些monad转换器可能会获得不错的语法,但是它们
所有单子都不存在(也不能存在)。 [2]

对于有变压器的单子,可能会出现您想要的东西:

1
2
3
4
5
6
7
val l: List[Int]      = List(1,2,3)
val o: Option[String] = Some("abc")

val ol: OptionT[List, String] = for {
  a <- OptionT.liftF(l)
  b <- OptionT.fromOption[List](o)
} yield a + b.toString

如果您对OptionT感兴趣,可以使用scala文档([3])。

这是更好还是不好看在情人眼中。

可悲的是,如果您更经常需要此功能,则可能需要按照

的方式编写自己的帮助程序功能

1
2
3
4
5
6
def combine2[F[_]: Functor, G[_]: Functor, A, B, C](
    fa: F[A],
    gb: G[B])(
    f: (A, B) => C
  ): F[G[C]] =
    fa.map(a => gb.map(b => (f(a, b))))

如果您不想这样做,您可以做的一件事就是已经提到的@ andrey-tyukin。但我同意,最好只嵌套对map的调用。

您可能要看的另一件事是Nested [4]。在这种特殊情况下,它无济于事,但可以帮助您减少一些嵌套的map

-

[1] http://blog.tmorris.net/posts/monads-do-not-compose/

[2]为什么Haskell中没有IO转换器?

[3] https://typelevel.org/cats/api/cats/data/OptionT.html

[4] https://typelevel.org/cats/api/cats/data/Nested.html