Task
(scalaz或更好FS2)应满足所有的要求,它不需要单子变压器,因为它是已经Either
内(Either
对于FS2,\/
为scalaz)。它也具有您需要的快速失败行为,与右偏分离/异或相同。
以下是已知会我几个实现:
不管单子变压器没有,你还在使用Task
时还挺需要提升:
但是,是的,它似乎比单子变压器更简单,尤其是单子几乎不可组合的事实 - 为了定义m onad变压器,你必须知道一些关于你的类型的其他细节,除了是一个单子(通常它需要像comonad提取价值)。
仅用于广告目的,我还会补充说Task
表示堆栈安全的蹦床计算。
不过,也有一些项目专注于扩展单子组成,像EMM-单子:https://github.com/djspiewak/emm,这样你就可以撰写单子变压器与Future
/Task
,Either
,Option
,List
等等等等。但是,国际海事组织与Applicative
相比仍然有限 - cats
提供了通用的Nested
数据类型,可以轻松组成任何应用程序,您可以找到一些示例in this answer - 这里唯一的缺点是使用Applicative很难构建可读的DSL。另一种选择是所谓的“自由单体”:https://github.com/m50d/paperdoll,它基本上提供了更好的构图,并且允许将不同的效果层分成不同的解释器。
例如,如没有FutureT
/变压器你不能建立像type E = Option |: Task |: Base
(Option
从Task
)的效果,例如flatMap
将需要从Future
/Task
的值提取。
作为一个结论,我可以说从我的经验Task
真的出现在基于do-notation的DSL中:我有一个复杂的外部规则 - 如异步计算的DSL,当我决定将它全部迁移到Scala-嵌入式版本Task
真的帮助 - 我从字面上将外部DSL转换为Scala的for-comprehension
。我们考虑的另一件事是自定义类型,例如ComputationRule
,其中定义了一组类型类以及Task
/Future
或任何我们需要的转换,但这是因为我们没有明确使用Free
-monad。
你可能甚至不需要在这里Free
-monad假设你不需要切换口译的能力(可能为只是系统测试是真实的)。在这种情况下Task
可能是你唯一需要的东西 - 这是懒惰的(与未来比较),真正做到了功能和堆栈安全:
trait DSL {
def put[E](e: E): Task[Unit]
def count[E](e: E): Task[Int]
}
object Implementation1 extends DSL {
...implementation
}
object Implementation2 extends DSL {
...implementation
}
//System-test script:
def test0(dsl: DSL) = {
import dsl._
for {
_ <- put("Apple")
_ <- put("Orange")
_ <- put("Pinneaple")
nApples <- count("Apple")
nPears <- count("Pear")
nBananas <- count("Banana")
} yield List(("Apple", nApples), ("Pears", nPears), ("Bananas", nBananas))
}
所以,你可以通过不同的交换机执行“解释”在这里:
test0(Implementation1).unsafeRun
test0(Implementation2).unsafeRun
差异/缺点(与http://typelevel.org/cats/datatypes/freemonad.html比较):
- 你坚持
Task
类型,所以你不能把它崩来点它很容易monad。
实现在运行时通过DSL特性的实例(而不是自然转换)解决,您可以使用eta-expansion:test0 _
轻松进行抽象。 Java/Scala自然支持多态方法(put,count),但poly函数并不是那么容易通过包含T => Task[Unit]
(对于put
操作)的DSL
实例,而不是使用自然变换DSLEntry ~> Task
来生成合成多态函数DSLEntry[T] => Task[Unit]
。
没有明确AST作为替代模式自然转化内匹配 - 我们使用静态调度(显式调用的方法,这将返回懒计算)DSL特质里面
事实上,你甚至可以摆脱这里的Task
:
trait DSL[F[_]] {
def put[E](e: E): F[Unit]
def count[E](e: E): F[Int]
}
def test0[M[_]: Monad](dsl: DSL[M]) = {...}
所以在这里它甚至可能成为首特别是当你不写一个开放源码库的问题。
全部放在一起:
import cats._
import cats.implicits._
trait DSL[F[_]] {
def put[E](e: E): F[Unit]
def count[E](e: E): F[Int]
}
def test0[M[_]: Monad](dsl: DSL[M]) = {
import dsl._
for {
_ <- put("Apple")
_ <- put("Orange")
_ <- put("Pinneaple")
nApples <- count("Apple")
nPears <- count("Pear")
nBananas <- count("Banana")
} yield List(("Apple", nApples), ("Pears", nPears), ("Bananas", nBananas))
}
object IdDsl extends DSL[Id] {
def put[E](e: E) =()
def count[E](e: E) = 5
}
注意,猫有一个Monad
为Id
定义的,因此:
scala> test0(IdDsl)
res2: cats.Id[List[(String, Int)]] = List((Apple,5), (Pears,5), (Bananas,5))
简单的工作。当然,如果您愿意,您可以选择Task
/Future
/Option
或任意组合。作为事实上,你可以使用Applicative
代替Monad
:
def test0[F[_]: Applicative](dsl: DSL[F]) =
dsl.count("Apple") |@| dsl.count("Pinapple apple pen") map {_ + _ }
scala> test0(IdDsl)
res8: cats.Id[Int] = 10
|@|
是一个平行的运营商,所以你可以使用cats.Validated
代替Xor
,注意|@|
任务不执行(至少在旧的斯拉拉版本)并行(并行运算符不等于并行计算)。您也可以使用两者的结合:
import cats.syntax._
def test0[M[_]:Monad](d: DSL[M]) = {
for {
_ <- d.put("Apple")
_ <- d.put("Orange")
_ <- d.put("Pinneaple")
sum <- d.count("Apple") |@| d.count("Pear") |@| d.count("Banana") map {_ + _ + _}
} yield sum
}
scala> test0(IdDsl)
res18: cats.Id[Int] = 15
'Task'(scalaz或更好FS2)应满足所有的要求,它不需要单子变压器,因为它是已经有了要么内(无论是FS2, \/for scalaz)。它也具有你需要的快速失败行为,与正确的偏向/异或行相同。 – dk14
我不知道“Task”,很好。这种方法似乎也暗示了人们如何在Scala世界中构建monad,忘记Haskell中的'lift'运算符,定义您需要混合的所有方面(例如并发和错误处理)的自己的类,并定义一个monad实例。 –
当使用'Task'时,你仍然需要解除,从值提升到Task,或者从Either到Task。但是,是的,它似乎比monad变换器更简单,尤其是在monad几乎不可组合的情况下(为了定义monad变换器,除了作为monad外,你还需要知道关于你的类型的一些其他细节 - 通常它需要像comonad提取价值)。仅仅为了广告的目的,我还要补充一点,'Task'表示堆栈安全的蹦床计算。但是有一些项目着重于一元组合,比如'Emm-monad' – dk14