scalaz使用

在“ Scalaz日常使用功能”的第二篇文章中,我们将探讨Monad变压器和Reader monad的主题。让我们从Monad变压器开始。 当您不得不处理嵌套的Monad时,Monad变压器会派上用场,这种情况经常发生。 例如,当您必须使用嵌套的Future [Option]或Future [Either]时,您的理解很快就会变得不可读,因为您必须明确处理OptionNoneSome情况以及SuccessFailure情况。 在本文中,我将展示一些Monad变压器派上用场的示例,以及如何使用它们。

本系列当前提供以下文章:

  • 日常使用的Scalaz功能第1部分:Typeclasses和Scala扩展
  • 日常使用的Scalaz功能第2部分:Monad变形金刚和Reader Monad

在没有Monad变压器的情况下工作

就像我们在简介中提到的那样,Monad转换器在处理嵌套Monad时非常有用。 但是,什么时候会遇到这些? 好吧,很多Scala数据库库都倾向于异步(使用Futures ),在某些情况下会返回Options 。 例如,您可以查询返回Future [Option [T]]的特定记录:

# from Phantom cassandra driver (without implicits), which returns Some(Record) if found
# or None if nothing can be found
def one(): Future[Option[Record]]# or you might want to get the first element from a Slick query
val res : Future[Option[Row]] = db.run(filterQuery(id).result.headOption)

或者,您可能只是拥有自己的特征或服务定义函数,这些函数最终将最终返回OptionEither

# get an account, or none if no account is found
def getAccount() : Future[Option[Account]]# withdraw an amount from an account, returning either the new amount in the account
# or a message explaining what went wrong
def withdraw(account: Account, amount: Amount) : Future[\/[String, Amount]]

让我们看一个不使用Monad转换器时会得到的丑陋代码示例:

def withdrawWithoutMonadTransformers(accountNumber: String, amount: Amount) : Future[Option[Statement]] = {for {// returns a Future[Option[Account]]account <- Accounts.getAccount(accountNumber)// we can do a fold, using scalaz for the typed None, since a None isn't typedbalance <- account.fold(Future(none[Amount]))(Accounts.getBalance(_))// or sometimes we might need to do a patten match, since we've got two options_ <- (account, balance) match {case (Some(acc), Some(bal)) => Future(Accounts.withdraw(acc,bal))case _ => Future(None)}// or we can do a nested mapstatement <- Future(account.map(Accounts.getStatement(_)))} yield statement
}

如您所见,当我们需要使用嵌套的Monad时,我们需要在理解的每一步的右侧处理嵌套的Monad。 Scala的语言足够丰富,我们可以用很多不同的方法来做到这一点,但是代码的可读性却不高。 如果我们对多个Option感兴趣,我们必须借助折叠来求助于嵌套地图或平面图的使用(对于Option而言 ),有时不得不求助于模式匹配。 可能还有其他方法可以执行此操作,但是总而言之,代码的可读性更高。 由于我们必须显式地处理嵌套的Option。

现在有了Monad变压器

使用Monad变压器,我们可以删除所有这些样板,并获得使用此类嵌套结构的非常方便的方法。 Scalaz提供以下类型的Monad变压器:

BijectionT
EitherT
IdT
IndexedContsT
LazyEitherT
LazyOptionT
ListT
MaybeT
OptionT
ReaderWriterStateT
ReaderT
StateT
StoreT
StreamT
UnWriterT
WriterT

尽管其中一些可能看起来有些怪异 ListTOptionTEitherTReaderTWriterT可以应用于很多用例。 在第一个示例中,我们将重点介绍OptionT Monad变压器。 首先让我们看一下如何创建OptionT monad。 对于我们的示例,我们将创建一个OptionT [Future,A] ,它将Option [A]包装在Future中 。 我们可以从A像这样创建这些:

scala> :require /Users/jos/.ivy2/cache/org.scalaz/scalaz-core_2.11/bundles/scalaz-core_2.11-7.2.1.jar
Added '/Users/jos/.ivy2/cache/org.scalaz/scalaz-core_2.11/bundles/scalaz-core_2.11-7.2.1.jar' to classpath.scala> import scalaz._
import scalaz._scala> import Scalaz._
import Scalaz._                                ^scala> import scala.concurrent.Future
import scala.concurrent.Futurescala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.globalscala> type Result[A] = OptionT[Future, A]
defined type alias Resultscala> 1234.point[Result]
res1: Result[Int] = OptionT(scala.concurrent.impl.Promise$DefaultPromise@1c6ab85)scala> "hello".point[Result]
res2: Result[String] = OptionT(scala.concurrent.impl.Promise$DefaultPromise@3e17219)scala> res1.run
res4: scala.concurrent.Future[Option[Int]] = scala.concurrent.impl.Promise$DefaultPromise@1c6ab85

请注意,我们定义了显式类型Result以便能够使工作。 如果不这样做,您将获得有关类型构造的有用错误消息:

scala> "why".point[OptionT[Future, String]]
<console>:16: error: scalaz.OptionT[scala.concurrent.Future,String] takes no type parameters, expected: one"why".point[OptionT[Future, String]]

仅在处理monad转换器的内部时才能使用point 。 如果您已经拥有FutureOption ,则需要使用OptionT构造函数。

scala> val p: Result[Int] = OptionT(Future.successful(some(10)))
p: Result[Int] = OptionT(scala.concurrent.impl.Promise$KeptPromise@40dde94)

使用Monad变压器,我们可以自动拆开嵌套的Monad。 现在,我们现在将如何将值转换为OptionT,让我们回顾一下之前看到的示例,并像这样重写它:

def withdrawWithMonadTransformers(accountNumber: String, amount: Amount) : Future[Option[Statement]] = {type Result[A] = OptionT[Future, A]val result = for {account <- OptionT(Accounts.getAccount(accountNumber))balance <- OptionT(Accounts.getBalance(account))_ <- OptionT(Accounts.withdraw(account,balance).map(some(_)))statement <- Accounts.getStatement(account).point[Result]} yield statementresult.run
}

不错吧? 无需将所有噪声归纳为正确的类型,我们只需创建OptionT实例并将其返回即可。 为了从OptionT中获取储值,我们只需要调用run

即使它已经更具可读性。 现在,我们有了创建OptionT的麻烦 。 即使噪音不大,它仍然会让人分心。

还有更多语法清除

我们甚至可以多清理一点:

// with Monad transformers
type Result[A] = OptionT[Future, A]/*** Unfortunately we can't use overloading, since we then run into* type erase stuff, and the thrush operator not being able to find* the correct apply function*/
object ResultLike {def applyFO[A](a: Future[Option[A]]) : Result[A] = OptionT(a)def applyF[A](a: Future[A]) : Result[A] = OptionT(a.map(some(_)))def applyP[A](a: A) : Result[A] = a.point[Result]
}def withdrawClean(accountNumber: String, amount: Amount) : Future[Option[Statement]] = {val result: Result[Statement] = for {account <- Accounts.getAccount(accountNumber)         |> ResultLike.applyFObalance <- Accounts.getBalance(account)               |> ResultLike.applyFO_ <- Accounts.withdraw(account,balance)               |> ResultLike.applyFstatement <- Accounts.getStatement(account)           |> ResultLike.applyP} yield statementresult.run
}

在这种方法中,我们仅创建特定的转换器以将结果转换为OptionT monad。 结果是,实际的理解非常容易理解,没有任何混乱。 在右侧,出于非直接视力考虑,我们进行了向OptionT的转换。 请注意,这不是最干净的解决方案,因为我们需要指定不同的Apply函数。 这里的重载不起作用,因为在类型擦除之后, applyFOapplyF将具有相同的签名。

读者单子

Reader Monad是Scalaz提供的标准Monad之一。 Reader monad可用于轻松传递配置(或其他值),并可用于诸如依赖性注入之类的东西。

Reader monad解决方案

Reader monad允许您在scala中进行依赖项注入。 该依赖关系是否是配置对象,还是对其他服务的引用,实际上并没有那么多。 我们从一个例子开始,因为这最好地说明了如何使用阅读器monad。

对于此示例,我们假设我们有一项服务,该服务需要Session才能完成工作。 这可以是数据库会话,某些Web服务会话或其他。 因此,让我们将之前的示例替换为该示例,现在通过删除期货来对其进行简化:

trait AccountService {def getAccount(accountNumber: String, session: Session) : Option[Account]def getBalance(account: Account, session: Session) : Option[Amount]def withdraw(account: Account, amount: Amount, session: Session) : Amountdef getStatement(account: Account, session: Session): Statement
}object Accounts extends AccountService {override def getAccount(accountNumber: String, session: Session): Option[Account] = ???override def getBalance(account: Account, session: Session): Option[Amount] = ???override def withdraw(account: Account, amount: Amount, session: Session): Amount = ???override def getStatement(account: Account, session: Session): Statement = ???
}

这似乎有点烦人,因为每次我们要调用其中一个服务时,我们都需要提供Session的实现。 我们当然可以使Session隐式,但是当我们调用该服务的功能时,我们仍然需要确保它在范围内。 如果我们能够找到一种以某种方式注入此会话的方法,那就太好了。 我们当然可以在此服务的构造函数中执行此操作,但是我们也可以为此使用Reader Reader,将代码更改为:

// introduce a Action type. This represents an action our service can execute. As you can see in
// the declaration, this Action, requires a Session.
type Action[A] = Reader[Session, A]trait AccountService {// return an account, or return none when account can't be founddef getAccount(accountNumber: String) : Action[Option[Account]]// return the balance when account is opened, or none when it isn't opened yetdef getBalance(account: Account) :Action[Option[Amount]]// withdraw an amount from the account, and return the new amountdef withdraw(account: Account, amount: Amount) : Action[Amount]// we can also get an account overview statement, which somehow isn't asyncdef getStatement(account: Account): Action[Statement]
}object Accounts extends AccountService {override def getAccount(accountNumber: String): Action[Option[Account]] = Reader((session: Session) => {// do something with session here, and return resultsession.doSomethingsome(Account())})override def getBalance(account: Account): Action[Option[Amount]] = Reader((session: Session) => {// do something with session here, and return resultsession.doSomethingsome(Amount(10,"Dollar"))})override def withdraw(account: Account, amount: Amount): Action[Amount] = Reader((session: Session) => {// do something with session here, and return resultsession.doSomethingAmount(5, "Dollar")})override def getStatement(account: Account): Action[Statement] = Reader((session: Session) => {// do something with session here, and return resultsession.doSomethingStatement(account)})
}

如您所见,我们没有返回结果,而是将结果包装在Reader中 。 很酷的事情是,由于Reader只是monad,我们现在可以开始编写东西了。

def withdrawWithReader(accountNumber: String) = {for {account <- Accounts.getAccount(accountNumber)balance <- account.fold(Reader((session: Session) => none[Amount]))(ac => Accounts.getBalance(ac))_ <- (account, balance) match {case (Some(acc), Some(bal)) => Accounts.withdraw(acc,bal)case _ => Reader((session: Session) => none[Amount])}statement <- account match { case Some(acc) => Accounts.getStatement(acc)}} yield statement
}

这不会返回实际的最终值,但会返回Reader 。 现在,我们可以通过传递Session来运行代码:

// function returns 'steps' to execute, run execute these steps in the context of 'new Session'
withdrawWithReader("1234").run(new Session())

当您回顾withdrawWithReader函数时,您会发现我们再次必须显式管理Option monad,并确保始终创建一个Reader 。 幸运的是,Scalaz还提供了ReaderT ,我们可以使用它来自动处理特定类型的Monad。 在以下代码中,我们显示了此示例的操作方法:

// introduce a Action type. This represents an action our service can execute. As you can see in
// the declaration, this Action, requires a Session.
type Action[A] = ReaderT[Option, Session, A]trait AccountService {// return an account, or return none when account can't be founddef getAccount(accountNumber: String) : Action[Account]// return the balance when account is opened, or none when it isn't opened yetdef getBalance(account: Account) :Action[Amount]// withdraw an amount from the account, and return the new amountdef withdraw(account: Account, amount: Amount) : Action[Amount]// we can also get an account overview statement, which somehow isn't asyncdef getStatement(account: Account): Action[Statement]
}object Accounts extends AccountService {override def getAccount(accountNumber: String): Action[Account] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomethingsome(Account())})override def getBalance(account: Account): Action[Amount] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomethingsome(Amount(10,"Dollar"))})override def withdraw(account: Account, amount: Amount): Action[Amount] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomethingSome(Amount(5, "Dollar"))})override def getStatement(account: Account): Action[Statement] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomethingSome(Statement(account))})
}def withdrawWithReaderT(accountNumber: String) = {for {account <- Accounts.getAccount(accountNumber)balance <- Accounts.getBalance(account)_ <- Accounts.withdraw(account, balance)statement <- Accounts.getStatement(account)} yield statement
}withdrawWithReaderT("1234").run(new Session)

如您所见,变化不大。 我们所做的主要更改是将Action的声明更改为使用ReaderT而不是Reader ,并且我们更改了特性和实现以使用它。 现在,当您查看withdrawWithReaderT函数时,您会看到我们不再需要处理Option了,但是它是由我们的ReaderT处理的(实际上是Kleisli,但这是另一把子弹的东西)。 酷吧?

尽管这对于Option而言非常有用 ,但是如果我们回到原始示例并想处理嵌套在Future中Option以及再次在Reader中嵌套的Option会发生什么呢? 到那时,我们可能超出了“ Scalaz日常使用功能”的范围,但是基本设置是相同的:

// introduce a Action type. This represents an action our service can execute. As you can see in
// the declaration, this Action, requires a Session.
type OptionTF[A] = OptionT[Future, A]
type Action[A] = ReaderT[OptionTF, Session, A]trait AccountService {// return an account, or return none when account can't be founddef getAccount(accountNumber: String) : Action[Account]// return the balance when account is opened, or none when it isn't opened yetdef getBalance(account: Account) :Action[Amount]// withdraw an amount from the account, and return the new amountdef withdraw(account: Account, amount: Amount) : Action[Amount]// we can also get an account overview statement, which somehow isn't asyncdef getStatement(account: Account): Action[Statement]
}/*** Normally you would wrap an existing service, with a readerT specific one, which would handle* all the conversion stuff.*/
object Accounts extends AccountService {override def getAccount(accountNumber: String): Action[Account] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomething// Assume we get a Future[Option[Account]]val result = Future(Option(Account()))// and we need to lift it in the OptionTF and return it.val asOptionTF: OptionTF[Account] = OptionT(result)asOptionTF})override def getBalance(account: Account): Action[Amount] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomething// assume we get a Future[Option[Amount]]val result = Future(some(Amount(10,"Dollar")))// convert it to the Action type, with explicit type to make compiler happyval asOptionTF: OptionTF[Amount] = OptionT(result)asOptionTF})override def withdraw(account: Account, amount: Amount): Action[Amount] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomething// assume we get a Future[Amount]val result = Future(Amount(5, "Dollar"))// convert it to the correct typeval asOptionTF: OptionTF[Amount] = OptionT(result.map(some(_)))asOptionTF})override def getStatement(account: Account): Action[Statement] = ReaderT((session: Session) => {// do something with session here, and return resultsession.doSomething// assume we get a Statementval result = Statement(account)// convert it to the correct typeresult.point[OptionTF]})
}def withdrawWithReaderT(accountNumber: String) = {for {account <- Accounts.getAccount(accountNumber)balance <- Accounts.getBalance(account)_ <- Accounts.withdraw(account, balance)statement <- Accounts.getStatement(account)} yield statement
}// this is the result wrapped in the option
val finalResult = withdrawWithReaderT("1234").run(new Session)
// get the Future[Option] and wait for the result
println(Await.result(finalResult.run, 5 seconds))

我们定义了一个不同的ReaderT类型,在这里我们传入OptionT而不是Option 。 该OptionT将处理Option / Future转换。 当我们有了新的ReaderT时,我们当然需要将服务调用的结果提升为该monad,这需要某种类型的强制性才能使编译器理解所有内容(Intellij,也不再对此有所了解)。 结果虽然很好。 实际的理解力保持不变,但是这次可以在Reader内部处理Future内部的Option了!

结论

在本文中,我们研究了Scalaz的两个部分,它们在处理嵌套的monad或希望更好地管理组件之间的依赖关系时非常有用。 很棒的事情是,将Monad Transformers与Reader monad一起使用非常容易。 总体结果是,通过几个小步骤,我们可以完全隐藏使用FutureOption手的工作细节(在这种情况下),并具有很好的理解力和其他优点。

翻译自: https://www.javacodegeeks.com/2016/05/scalaz-features-everyday-usage-part-2-monad-transformers-reader-monad.html

scalaz使用

scalaz使用_日常使用的Scalaz功能第2部分:Monad变形金刚和Reader Monad相关推荐

  1. 日常使用的Scalaz功能第2部分:Monad变形金刚和Reader Monad

    在" Scalaz日常使用功能"的第二篇文章中,我们将探讨Monad变压器和Reader monad的主题.让我们从Monad变压器开始. 当您不得不处理嵌套的Monad时,Mon ...

  2. 分页技巧_实现第一个分页功能(回复列表中的分页)

    分页技巧_实现第一个分页功能(回复列表中的分页) ======================================== 假设共25条数据,每页显示10条,则共3页 first  max - ...

  3. 烛光晚餐矢量图(编号:82204)_日常生活_矢量人物_矢量素材

    烛光晚餐矢量图(编号:82204)_日常生活_矢量人物_矢量素材 烛光晚餐矢量图(编号:82204)_日常生活_矢量人物_矢量素材 posted on 2013-12-03 10:58 lexus 阅 ...

  4. HTML5七夕情人节表白网页_(唯美满天星)多功能展示(网状球状)3D相册_HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册

    HTML5七夕情人节表白网页_(唯美满天星)多功能展示(网状球状)3D相册_HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花 ...

  5. 刷脸支付赋予日常场景更多的功能和应用

    刷脸支付推出将近一年,已广泛应用于餐饮零售等商业场景.据预测,2019年POS机总保有量约为5000万台,这也意味着刷脸支付至少有500亿的市场空间.不少连锁餐饮品牌相继引入刷脸支付收银机,除了可以提 ...

  6. 探花交友_第5章_圈子、小视频功能实现

    探花交友_第5章_圈子.小视频功能实现 文章目录 探花交友_第5章_圈子.小视频功能实现 1.圈子点赞实现分析 2.点赞 2.1.定义枚举 2.2.dubbo服务 2.2.1.定义接口 2.2.2.编 ...

  7. 探花交友_第8章_搜附近以及探花功能实现

    探花交友_第8章_搜附近以及探花功能实现 文章目录 探花交友_第8章_搜附近以及探花功能实现 1.上报地理位置 1.1.dubbo服务 1.1.1.创建工程 1.1.2.定义pojo 1.1.3.定义 ...

  8. 一机一码加密软件_加密软件还有哪些功能?

    加密软件是办公中常用的一种软件,大家对文件加密也有一定的熟知度,文件除了针对电脑文件防外泄,在日常生活中,我们对文件加密使用的频率较高,所以相对也比较了解,那么加密软件还有哪些功能呢? 一.权限管理 ...

  9. 慈溪微生活图标_日常生活中的图标

    慈溪微生活图标 Even though we may not always notice them, Icons are all around us. They're found on our fav ...

最新文章

  1. [题解]UVA10054 The Necklace
  2. gogs mysql 报错_linux上Docker安装gogs私服亲测(详解)
  3. 中国膏剂(膏方)行业运营模式及十四五前景预测报告2022年版
  4. Linux生成ssh公钥免密码登录远程主机和Xshell跨跳板机登录
  5. ansole终端链接linux,基于Linux系统的智能家居远程控制系统设计论文.doc
  6. html 横屏内容显示不全_为什么我的文本显示不全?
  7. Easy Tech:什么是I帧、P帧和B帧?
  8. 国家开放大学2021春1127实用卫生统计学题目
  9. Linux中wait()函数及waitpid()函数
  10. C#设计模式之19-观察者模式
  11. python中int对象不可迭代_python - 情感分析接收错误:'int'对象不可迭代_python-3.x_酷徒编程知识库...
  12. HbuilderX配置微信开发者工具
  13. USB - DFU(dfuse\stm32)、fastboot、dfu-util
  14. ggplot多图叠加_R作图 ggplot2图片的布局排版
  15. node生成图形验证码
  16. 葡萄牙晋级世界杯决赛
  17. 提高钢材品质应用 高精度在线测径仪
  18. 数字电子技术基础——第一章 绪论(笔记)
  19. 省级面板数据(2000-2019)十八:物质资本(原始数据、测算数据)(stata版本)
  20. 李京:中国科技大学移动平台——掌上科大

热门文章

  1. 禅与摩托车维修艺术思想哲学
  2. linux u盘 慢_在Deepin V20系统下用USB3.0传输速度慢或许是共有问题
  3. leetcode 5370. 设计地铁系统(C++)
  4. logging 日志输出
  5. 002基于小波的多类癫痫类型分类系统-2021
  6. yeelink服务器稳定吗,一步步教你使用云端服务器yeelink远程监控
  7. Mybatis OTHERWISE标签
  8. 怎样理解“全连接”和“局部连接”
  9. excel自动筛选_在Excel自动筛选器中隐藏箭头
  10. 游戏编程之十六 扩展(DDEX2和DDEX3)