加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala中的未来是monad吗?

发布时间:2020-12-16 09:47:02 所属栏目:安全 来源:网络整理
导读:为什么和具体是什么Scala未来不是一个Monad;并有人会请比较它的东西,是一个Monad,像一个选项? 我要求的原因是丹尼尔·韦斯海德的The Neophyte’s Guide to Scala Part 8: Welcome to the Future,我问一个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)
> Right identity:bind(m){unit(_)}≡m
>关联性:bind(bind(m)(f))(g)≡bind(m){x = bind(f(x))(g)}

这些法律必须为所有可能的价值观通过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)

“不安全”函数运行任务,暴露任何内部效应作为副作用。所以尽量不要调用任何这些“不安全”的函数,直到你为你的整个程序组成一个巨大的任务。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读