Scalaz 单子变换器。将 f1:A => G[B], f2:B => G[C] 函数应用于 F[G[A]] 对象

Scalaz monad transformers. Applying f1:A => G[B], f2:B => G[C] function to F[G[A]] object

我有两个(或更多)函数定义为:

val functionM: String => Option[Int] = s => Some(s.length)
val functionM2: Int => Option[String] = i => Some(i.toString)

我还有一些数据定义为:

val data: List[Option[String]] = List(Option("abc"))

我的问题是如何(以一种很好的方式)编写函数以获得如下结果:

data.map(_.flatMap(functionM).flatMap(functionM2))
res0: List[Option[String]] = List(Some(3))

我不喜欢上面函数调用的语法。如果我有很多这样的地方,那么代码就很难读了。

我尝试使用 OptionT scalaz monad 转换器,但它仍然具有嵌套映射并且还生成嵌套选项,例如:

OptionT(data).map(a => functionM(a).map(functionM2)).run
res2: List[Option[Option[Option[String]]]] = List(Some(Some(Some(3))))

我想实现的大概是这样的:

Something(data).map(functionM).map(functionM2)

甚至更好:

val functions = functionM andThenSomething functionM2
Something(data).map(functions)

如果它能与 Try 一起工作就好了。 据我所知,scalaz 没有 TryT monad 转换器,所以有什么方法可以很好地组合在其上运行的函数尝试?

正如 Łukasz 所提到的,Kleisli 似乎与此最相关。任何时候你有一些形状为 A => F[B] 的函数,并且你想将它们组合成普通函数 A => B(并且你有一个 flatMap 代表 F),你可以将函数表示为 Kleisli 箭头:

import scalaz._, Scalaz._

val f1: Kleisli[Option, String, Int] = Kleisli(s => Some(s.length))
val f2: Kleisli[Option, Int, String] = Kleisli(i => Some(i.toString))

然后:

scala> f1.andThen(f2).run("test")
res0: Option[String] = Some(4)

如果您熟悉 reader monad 的概念,KleisliReaderT 完全相同——只是构建概念的一种稍微更通用的方式(有关详细信息,请参阅我的回答 )。

在这种情况下,monad 转换器似乎不太可能是您要找的东西,因为您并没有完全进入 List[Option[A]] 直接与 A 一起工作—你保持两个层次不同。鉴于上面 f1f2 的定义,我可能只写以下内容:

scala> val data: List[Option[String]] = List(Option("abc"))
data: List[Option[String]] = List(Some(abc))

scala> data.map(_.flatMap(f1.andThen(f2)))
res1: List[Option[String]] = List(Some(3))

最后,仅仅因为 Scalaz 没有为 Try 提供 Monad(或 Bind,这是您在这里需要的)实例,这并不意味着你不能自己写。例如:

import scala.util.{ Success, Try }

implicit val bindTry: Bind[Try] = new Bind[Try] {
  def map[A, B](fa: Try[A])(f: A => B): Try[B] = fa.map(f)
  def bind[A, B](fa: Try[A])(f: A => Try[B]): Try[B] = fa.flatMap(f)
}

val f1: Kleisli[Try, String, Int] = Kleisli(s => Success(s.length))
val f2: Kleisli[Try, Int, String] = Kleisli(i => Success(i.toString))

然后:

scala> val data: List[Try[String]] = List(Try("abc"))
data: List[scala.util.Try[String]] = List(Success(abc))

scala> data.map(_.flatMap(f1.andThen(f2)))
res5: List[scala.util.Try[String]] = List(Success(3))

有些人对 FunctorMonadBind 这样的实例在存在例外情况下对 Try 的合法性有一些担忧,这些人往往是大声的人,但我发现很难在意(在我看来,有 完全可以避免 Try)。