我有两个(或更多)函数定义为:
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上运行的函数?
答案 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 A
s—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).