I have two (or more) functions defined as:
val functionM: String => Option[Int] = s => Some(s.length)
val functionM2: Int => Option[String] = i => Some(i.toString)
I also have some data defined as:
val data: List[Option[String]] = List(Option("abc"))
My question is how to compose (in a nice way) the functions to get a result like:
data.map(_.flatMap(functionM).flatMap(functionM2))
res0: List[Option[String]] = List(Some(3))
I don't like the syntax of the above function calls. If i have many places like this then the code is very unreadable.
I tried to play with OptionT scalaz monad transformer, but it still has nested maps and also generates nested Options like:
OptionT(data).map(a => functionM(a).map(functionM2)).run
res2: List[Option[Option[Option[String]]]] = List(Some(Some(Some(3))))
What I want to achieve is something more or less like this:
Something(data).map(functionM).map(functionM2)
or even better:
val functions = functionM andThenSomething functionM2
Something(data).map(functions)
It would be nice if it could work with Try. As I know scalaz doesn't have TryT monad transformer, so is there any way to nicely compose functions which operates on Try?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With