Monad Transformers Scalaz. Application of the function f1: A => G [B], f2: B => G [C] to the object F [G [A]]

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 write (in a good way) functions to get the result, for example:

 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 such places, then the code is very unreadable.

I tried to play OptionA scalag monad with the transformer, but it still has nested maps, and also generates nested parameters, such as:

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

I want to achieve something more or less:

 Something(data).map(functionM).map(functionM2) 

or even better:

 val functions = functionM andThenSomething functionM2 Something(data).map(functions) 

It would be nice if he could work with Try. . As I know, scalaz does not have a Trash transformer, so is there a way to compose functions that work in Try well?

+5
source share
1 answer

As Lucas mentions, Kleisli seems most relevant. Every time you have some functions of the form A => F[B] , and you want to compose them as if they were ordinary functions A => B (and you have 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 are familiar with the idea of ​​a reader monad, Kleisli is exactly the same as ReaderT - this is just a slightly more general way of creating an idea (see my answer here for more details).

In this case, it seems unlikely that monad transformers are what you are looking for since you are not reaching all the way inside List[Option[A]] to work directly with A s - you keep the two levels different. Given the definitions of f1 and f2 above, I would simply 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)) 

Finally, just because Scalaz does not provide an instance of Monad (or the Bind that you need here) for Try , this does not mean that you cannot write your own. For instance:

 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 are concerned about the legality of an instance of Functor or Monad or Bind like this for Try , in the presence of exceptions, and these people are usually loud people, but I find this (in my opinion, there are better reasons to avoid Try at all).

+7
source

Source: https://habr.com/ru/post/1247199/


All Articles