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

时间:2016-04-15 09:23:54

标签: 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上运行的函数?

1 个答案:

答案 0 :(得分:7)

As Łukasz mentions, Kleisli seems most relevant here. Any time you have some functions of the shape A => F[B] and you want to compose them as if they were ordinary functions A => B (and you have a flatMap for F), you can represent the functions as Kleisli arrows:

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))

And then:

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

If you're familiar with the idea of the reader monad, Kleisli is exactly the same thing as ReaderT—it's just a slightly more generic way of framing the idea (see my answer here for more detail).

In this case it seems unlikely that monad transformers are what you're looking for, since you're not reaching all the way inside the List[Option[A]] to work directly with the As—you're keeping the two levels distinct. Given the definitions of f1 and f2 above, I'd probably just write the following:

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))

Lastly, just because Scalaz doesn't provide a Monad (or Bind, which is what you'd need here) instance for Try, that doesn't mean you can't write your own. For example:

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))

And then:

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))

Some people have some concerns about the lawfulness of a Functor or Monad or Bind instance like this for Try in the presence of exceptions, and these people tend to be loud people, but I find it hard to care (in my view there are better reasons to avoid Try altogether).