Scalaz monad变形金刚.将f1:A => G [B],f2:B => G [C]函数应用于F [G [A]]对象

Nie*_*Nic 5 functional-programming scala monad-transformers function-composition scalaz

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

val functionM: String => Option[Int] = s => Some(s.length)
val functionM2: Int => Option[String] = i => Some(i.toString)
Run Code Online (Sandbox Code Playgroud)

我也有一些数据定义为:

val data: List[Option[String]] = List(Option("abc"))
Run Code Online (Sandbox Code Playgroud)

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

data.map(_.flatMap(functionM).flatMap(functionM2))
res0: List[Option[String]] = List(Some(3))
Run Code Online (Sandbox Code Playgroud)

我不喜欢上面函数调用的语法.如果我有很多像这样的地方那么代码是非常难以理解的.

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

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

我想要达到的目标或多或少是这样的:

Something(data).map(functionM).map(functionM2)
Run Code Online (Sandbox Code Playgroud)

甚至更好:

val functions = functionM andThenSomething functionM2
Something(data).map(functions)
Run Code Online (Sandbox Code Playgroud)

如果它可以与Try一起使用会很好.据我所知,scalaz没有TryT monad变换器,所以有没有办法很好地组合在Try上运行的函数?

Tra*_*own 7

正如Łukasz所提到的,Kleisli这里似乎最相关.任何时候你有一些形状的功能,A => F[B]并且你想要将它们组合成普通的函数A => B(并且你有一个flatMapfor 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))
Run Code Online (Sandbox Code Playgroud)

然后:

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

如果你熟悉的读者单子的想法,Kleisli是完全一样的同样的事情ReaderT-它只是框架的构思的稍微更通用的方法(见我的答案在这里更多的细节).

在这种情况下,它似乎是不可能的单子变压器是你在找什么,因为你还没有在里面的所有的方式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))
Run Code Online (Sandbox Code Playgroud)

最后,仅仅因为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))
Run Code Online (Sandbox Code Playgroud)

然后:

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))
Run Code Online (Sandbox Code Playgroud)

有些人对一的合法性一些顾虑FunctorMonadBind例如像这样Try的异常的情况下,这些人往往是响亮的人,但我觉得很难照顾(我认为有更好的理由,以避免Try完全) .