Scalaz 单子变换器。将 f1:A => G[B], f2:B => G[C] 函数应用于 F[G[A]] 对象
Scalaz monad transformers. Applying f1:A => G[B], f2:B => G[C] function to F[G[A]] object
我有两个(或更多)函数定义为:
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 转换器,所以有什么方法可以很好地组合在其上运行的函数尝试?
正如 Łukasz 所提到的,Kleisli
似乎与此最相关。任何时候你有一些形状为 A => F[B]
的函数,并且你想将它们组合成普通函数 A => B
(并且你有一个 flatMap
代表 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))
然后:
scala> f1.andThen(f2).run("test")
res0: Option[String] = Some(4)
如果您熟悉 reader monad 的概念,Kleisli
与 ReaderT
完全相同——只是构建概念的一种稍微更通用的方式(有关详细信息,请参阅我的回答 )。
在这种情况下,monad 转换器似乎不太可能是您要找的东西,因为您并没有完全进入 List[Option[A]]
直接与 A
一起工作—你保持两个层次不同。鉴于上面 f1
和 f2
的定义,我可能只写以下内容:
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))
最后,仅仅因为 Scalaz 没有为 Try
提供 Monad
(或 Bind
,这是您在这里需要的)实例,这并不意味着你不能自己写。例如:
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))
然后:
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))
有些人对 Functor
或 Monad
或 Bind
这样的实例在存在例外情况下对 Try
的合法性有一些担忧,这些人往往是大声的人,但我发现很难在意(在我看来,有 完全可以避免 Try
)。
我有两个(或更多)函数定义为:
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 转换器,所以有什么方法可以很好地组合在其上运行的函数尝试?
正如 Łukasz 所提到的,Kleisli
似乎与此最相关。任何时候你有一些形状为 A => F[B]
的函数,并且你想将它们组合成普通函数 A => B
(并且你有一个 flatMap
代表 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))
然后:
scala> f1.andThen(f2).run("test")
res0: Option[String] = Some(4)
如果您熟悉 reader monad 的概念,Kleisli
与 ReaderT
完全相同——只是构建概念的一种稍微更通用的方式(有关详细信息,请参阅我的回答
在这种情况下,monad 转换器似乎不太可能是您要找的东西,因为您并没有完全进入 List[Option[A]]
直接与 A
一起工作—你保持两个层次不同。鉴于上面 f1
和 f2
的定义,我可能只写以下内容:
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))
最后,仅仅因为 Scalaz 没有为 Try
提供 Monad
(或 Bind
,这是您在这里需要的)实例,这并不意味着你不能自己写。例如:
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))
然后:
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))
有些人对 Functor
或 Monad
或 Bind
这样的实例在存在例外情况下对 Try
的合法性有一些担忧,这些人往往是大声的人,但我发现很难在意(在我看来,有 Try
)。