Scala中的未来是monad吗?
为什么和具体是什么Scala未来不是一个Monad;并有人会请比较它的东西,是一个Monad,像一个选项?
我要求的原因是丹尼尔·韦斯海德的The Neophyte’s Guide to Scala Part 8: Welcome to the Future,我问一个Scala未来是否是一个Monad,作者回答说,它不是,这扔了基地。我来这里要求澄清。 解决方法
摘要第一
如果你从来没有用有效块(纯内存计算)构建它们,或者如果生成的任何效果不被认为是语义等同(比如日志消息)的一部分,那么期货可以被认为是单子。然而,这不是大多数人在实践中使用它们。对于大多数使用有效期货(包括大多数Akka和各种Web框架的使用)的人来说,他们根本不是monad。 幸运的是,一个名为Scalaz的库提供了一个名为Task的抽象,它没有任何问题,也没有效果。 monad定义 让我们简单回顾一下monad是什么。 monad必须能够至少定义这两个函数: def unit[A](block: => A) : Future[A] def bind[A,B](fa: Future[A])(f: A => Future[B]) : Future[B] 这些功能必须统计三个规律: >左身份:bind(单位(a))(f)≡f(a) 这些法律必须为所有可能的价值观通过monad的定义。如果他们没有,那么我们根本没有一个monad。 还有其他方法来定义一个monad或多或少相同。这一个是流行的。 效果导致非值 几乎每一个我所看到的未来的使用它用于异步效果,输入/输出与外部系统,如Web服务或数据库。当我们这样做,一个未来甚至不是一个值,像monads这样的数学术语只描述价值观。 出现这个问题是因为期货立即执行数据处理。这混乱了用他们的评估值(有些人称为“引用透明度”)替换表达式的能力。这是了解为什么Scala的期货不足以实现功能编程的一种方式。 这里有一个例子说明这个问题。如果我们有两个效果: import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits._ def twoEffects = ( Future { println("hello") },Future { println("hello") } ) 我们将在调用twoEffects时有两个“hello”的打印: scala> twoEffects hello hello scala> twoEffects hello hello 但如果期货是价值,我们应该能够计算出共同的表达式: lazy val anEffect = Future { println("hello") } def twoEffects = (anEffect,anEffect) 但这不会给我们同样的效果: scala> twoEffects hello scala> twoEffects 第一次调用twoEffects运行效果并缓存结果,所以效果不会第二次调用twoEffects。 对于Futures,我们最终不得不考虑该语言的评估政策。例如,在上面的例子中,我使用惰性值而不是严格值的事实在操作语义上有所不同。这正是扭曲推理的功能编程的设计,以避免 – 它是通过编程的值。 没有替代,法律打破 在影响的推定,monad法律打破。从表面上看,法律似乎适用于简单情况,但是当我们开始用它们的评估值替代表达式时,我们最终遇到了上面所示的问题。我们根本不能谈论数学概念,如monad,当我们没有值在第一。 坦率地说,如果你使用效果与你的期货,说他们是monads是not even wrong,因为他们甚至不是值。 为了看看monad的法律如何破坏,只是因为你的有效的未来: import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits._ def unit[A] (block: => A) : Future[A] = Future(block) def bind[A,B] (fa: Future[A]) (f: A => Future[B]) : Future[B] = fa flatMap f lazy val effect = Future { println("hello") } 再次,它只会运行一次,但你需要它运行两次 – 一次在法律的右侧,另一个为左。我将说明正确的身份法的问题: scala> effect // RHS has effect hello scala> bind(effect) { unit(_) } // LHS doesn't 隐式ExecutionContext 没有在隐式作用域中放置ExecutionContext,我们不能在我们的monad中定义unit或bind。这是因为Scala API for Futures有这些签名: object Future { // what we need to define unit def apply[T] (body: ? T) (implicit executor: ExecutionContext) : Future[T] } trait Future { // what we need to define bind flatMap[S] (f: T ? Future[S]) (implicit executor: ExecutionContext) : Future[S] } 作为对用户的“方便”,标准库鼓励用户在隐式范围中定义执行上下文,但我认为这是一个巨大的漏洞,只是导致缺陷。计算的一个范围可以具有定义的一个执行上下文,而另一个范围可以具有另一个上下文定义 也许你可以忽略这个问题,如果你定义一个单元的实例,并绑定这两个操作到一个单一的上下文,并一致地使用这个实例。但这不是大多数人在做什么。大多数时候,人们使用Futures与for-yield的理解,成为map和flatMap调用。为了使for-yield语法正常工作,必须在某些非全局隐式作用域中定义一个执行上下文(因为for-yield不能为map和flatMap调用指定额外的参数)。 要清楚,Scala允许你使用很多事情与for-yield理解实际上不是monads,所以不要相信你有一个monad只是因为它与for-yield语法。 一个更好的方法 有一个很好的Scala名为Scalaz,它有一个称为scalaz.concurrent.Task的抽象。这种抽象不像标准库Future那样对数据构造产生影响。此外,任务实际上是一个monad。我们单一地组合任务(如果我们喜欢,我们可以使用for-yield语法),并且在编写时没有任何效果。我们有最后的程序,当我们组合单个表达式求值任务[单位]。这最终是我们的“主”函数,我们可以终于运行它。 这里有一个例子说明了如何用其相应的估计值替换Task表达式: import scalaz.concurrent.Task import scalaz.IList import scalaz.syntax.traverse._ def twoEffects = IList( Task delay { println("hello") },Task delay { println("hello") }).sequence_ 在调用twoEffects时,我们将有两个“hello”的打印: scala> twoEffects.run hello hello 如果我们考虑共同的效果, lazy val anEffect = Task delay { println("hello") } def twoEffects = IList(anEffect,anEffect).sequence_ 我们得到我们所期望的: scala> twoEffects.run hello hello 事实上,无论我们使用惰性值还是使用Task的严格值都不重要;我们得到你打印两次任一方式。 如果你想要功能性的程序,考虑使用任务随处可以使用期货。如果API强制Futures在你身上,你可以将Future转换为任务: import concurrent. { ExecutionContext,Future,Promise } import util.Try import scalaz./ import scalaz.concurrent.Task def fromScalaDeferred[A] (future: => Future[A]) (ec: ExecutionContext) : Task[A] = Task .delay { unsafeFromScala(future)(ec) } .flatMap(identity) def unsafeToScala[A] (task: Task[A]) : Future[A] = { val p = Promise[A] task.runAsync { res => res.fold(p failure _,p success _) } p.future } private def unsafeFromScala[A] (future: Future[A]) (ec: ExecutionContext) : Task[A] = Task.async( handlerConversion .andThen { future.onComplete(_)(ec) }) private def handlerConversion[A] : ((Throwable / A) => Unit) => Try[A] => Unit = callback => { t: Try[A] => / fromTryCatch t.get } .andThen(callback) “不安全”函数运行任务,暴露任何内部效应作为副作用。所以尽量不要调用任何这些“不安全”的函数,直到你为你的整个程序组成一个巨大的任务。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |