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

标签 scala functional-programming scalaz monad-transformers function-composition

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

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 转换器,那么有什么方法可以很好地组合在 Try 上运行的函数?

最佳答案

正如 Ł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完全一样— 这只是一种更通用的构想方式(有关更多详细信息,请参阅我的回答 here)。

在这种情况下,monad 转换器似乎不太可能是您要寻找的东西,因为您没有完全进入 List[Option[A]] 内部。直接与 A 合作s—您将两个级别保持不同。鉴于 f1 的定义和 f2上面,我可能只写以下内容:
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 不提供 Monad (或 Bind ,这是您在这里需要的) Try 的实例,这并不意味着您不能自己编写。例如:
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))

有些人对Functor的合法性有些担忧。或 MonadBind像这样的实例 Try在存在异常(exception)的情况下,这些人往往是吵闹的人,但我觉得很难在意(在我看来,有 better reasons 完全避免了 Try)。

关于Scalaz monad 转换器。将 f1 :A => G[B], f2:B => G[C] 函数应用于 F[G[A]] 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36643245/

相关文章:

scala - 使用 hprof 分析 scala for 循环

scala - 使用Scala宏生成方法

Scala "update"不可变对象(immutable对象)最佳实践

Haskell: "where"子句中的类型变量与其父项位于同一命名空间中吗?

scalaz - 链接 Scalaz 镜头组操作

eclipse - 在 Eclipse 中,我可以为我的项目选择 Scala 版本吗?

json - 在 Scala 中转换 JSON 对象的问题

javascript - 在 JavaScript 中将方法作为参数传递

scala - 在 EitherT 上的左侧 flatMap

scala - 来自标准 IO 的 Iteratee 输入