为什么以及如何具体的Scala Future而不是Monad;有人请将它与Monad的东西进行比较,比如选项吗?
我问的原因是Daniel Westheide The Neophyte's Guide to Scala Part 8: Welcome to the Future我询问Scala Future是否是Monad,作者回答说它不是,它扔了离开基地。我来这里要求澄清。
答案 0 :(得分:88)
如果您从未使用有效块(纯粹的内存计算)构建它们,或者如果生成的任何效果不被视为语义等效的一部分(如记录消息),则可以将期货视为monads。但是,这并不是大多数人在实践中使用它们的方式。对于大多数使用有效期货(包括Akka的大多数用途和各种网络框架)的人来说,他们根本就不是单子。
幸运的是,一个名为Scalaz的库提供了一个名为Task的抽象,它在有或没有效果的情况下都没有任何问题。
让我们简要回顾一下monad是什么。 monad必须能够至少定义这两个函数:
def unit[A](block: => A)
: Future[A]
def bind[A, B](fa: Future[A])(f: A => Future[B])
: Future[B]
这些功能必须满足三个法则:
bind(unit(a))(f) ≡ f(a)
bind(m) { unit(_) } ≡ m
bind(bind(m)(f))(g) ≡ bind(m) { x => bind(f(x))(g) }
这些法律必须通过定义monad来保留所有可能的值。如果他们没有,那么我们根本就没有单子。
还有其他方法可以定义或多或少相同的monad。这个很受欢迎。
我所看到的几乎所有Future的使用都将它用于异步效果,使用外部系统(如Web服务或数据库)进行输入/输出。当我们这样做时,未来甚至不是一个价值,像monad这样的数学术语只描述价值。
出现此问题是因为期货在数据构建后立即执行。这会混淆用表达式替换表达式的能力(有些人称之为#34;参考透明度")。这是理解为什么Scala的期货不适合具有效果的函数式编程的一种方法。
以下是问题的说明。如果我们有两个影响:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits._
def twoEffects =
( Future { println("hello") },
Future { println("hello") } )
我们将打印两张"你好"在致电twoEffects
时:
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
时效果不会运行。
对于期货,我们最终不得不考虑该语言的评估政策。例如,在上面的例子中,我使用惰性值而不是严格值的事实在操作语义上有所不同。这正是函数式编程旨在避免的扭曲推理 - 它通过使用值编程来实现。
在效果的早期,monad法则破裂。从表面上看,法律似乎适用于简单的案例,但是当我们开始用表达式替换表达式时,我们最终会遇到上面说明的相同问题。当我们首先没有价值观时,我们根本无法谈论像monad这样的数学概念。
说得直白,如果你对期货使用效果,说他们的monad是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放在隐式范围内,我们就无法在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]
}
作为"方便"对于用户来说,标准库鼓励用户在隐式范围内定义执行上下文,但我认为这是API中的一个巨大漏洞,只会导致缺陷。一个计算范围可以定义一个执行上下文,而另一个范围可以定义另一个上下文。
如果您定义unit
和bind
的实例,将两个操作都固定到单个上下文并始终如一地使用此实例,则可以忽略此问题。但这不是人们大多数时间做的事情。大多数情况下,人们使用Futures来获得map
和flatMap
调用的for-yield理解。为了使for-yield理解起作用,必须在某些非全局隐式范围内定义执行上下文(因为for-yield不提供为map
和{{1}指定其他参数的方法。 }来电)。
为了清楚起见,Scala允许你使用很多东西,其中for-yield理解并不是真正的monad,所以不要因为它与for-yield语法一起工作而认为你有monad。
Scala的一个很好的库名为Scalaz,它有一个名为scalaz.concurrent.Task的抽象。这种抽象并不像标准库Future那样对数据构建产生影响。此外,Task实际上是一个monad。我们单独构成任务(如果我们愿意的话,我们可以使用for-yield理解),并且在我们编写时没有效果。当我们编写一个评估为flatMap
的表达式时,我们有了最终的程序。这最终成为我们相当于" main"功能,我们终于可以运行了。
这是一个示例,说明我们如何用各自的评估值替换任务表达式:
Task[Unit]
我们将打印两张"你好"在致电import scalaz.concurrent.Task
import scalaz.IList
import scalaz.syntax.traverse._
def twoEffects =
IList(
Task delay { println("hello") },
Task delay { println("hello") }).sequence_
时:
twoEffects
如果我们分解出共同的影响,
scala> twoEffects.run
hello
hello
我们得到了我们所期望的:
lazy val anEffect = Task delay { println("hello") }
def twoEffects =
IList(anEffect, anEffect).sequence_
事实上,我们是否使用惰性值或严格的值与Task相关并不重要;无论如何我们两次打印你好。
如果您想进行功能性编程,请考虑在任何地方使用任务,您可以使用Futures。如果API迫使您使用Futures,您可以将Future转换为任务:
scala> twoEffects.run
hello
hello
"不安全"函数运行任务,将任何内部效果暴露为副作用。因此,尽量不要将这些中的任何一个称为“不安全的”#34;功能,直到你为整个程序组成一个巨大的任务。
答案 1 :(得分:2)
正如其他评论者所说,你错了。 Scala的Future
类型具有 monadic属性:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits._
def unit[A](block: => A): Future[A] = Future(block)
def bind[A, B](fut: Future[A])(fun: A => Future[B]): Future[B] = fut.flatMap(fun)
这就是为什么你可以在Scala中使用带有期货的for
- 理解语法。
答案 2 :(得分:2)
我相信未来是一个Monad,具有以下定义:
def unit[A](x: A): Future[A] = Future.successful(x)
def bind[A, B](m: Future[A])(fun: A => Future[B]): Future[B] = fut.flatMap(fun)
考虑到三个法律:
左侧身份:
Future.successful(a).flatMap(f)
相当于f(a)
。检查。
正确的身份:
m.flatMap(Future.successful _)
相当于m
(减去一些可能的性能影响)。检查。
关联性
m.flatMap(f).flatMap(g)
相当于m.flatMap(x => f(x).flatMap(g))
。检查。
monad定律中等价的含义,据我所知,你可以在代码的另一面替换表达式的一面而不改变程序的行为。假设您始终使用相同的执行上下文,我认为就是这种情况。在@sukant给出的示例中,如果使用Option
而不是Future
,则会遇到相同的问题。我不认为期货得到热切评估的事实是相关的。