在“ 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单声道。 结果是,实际的理解非常容易理解,没有任何混乱。 在右侧,出于非直接视力的考虑,我们进行了向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中进行处理,将会发生什么? 好的,到那时,我们可能超出了“ 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功能第2部分:Monad变形金刚和Reader Monad相关推荐

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

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

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

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

  3. 利用PPT的平滑变换功能以及Onekey插件做变形金刚变身的过程

      无聊闹着玩,有兴趣可以看看,做一个图只要几分钟,不会太浪费时间 ①下载好口袋口袋动画PA插件,选择矢量素描,目的是把图片转换成矢量图,然后 ②选择,灰度矢量化,提高颜色灰度值,直到图中的空白点越来 ...

  4. usb扩展坞同时接键盘鼠标_这个多功能扩展坞,增加多个接口,笔记本秒变工作站...

    原标题:这个多功能扩展坞,增加多个接口,笔记本秒变工作站 为了给笔记本电脑"减负" 越做越薄,接口也是越来越少 很多接口功能都被省去 ... 可是想接入的设备却有很多:鼠标.U盘. ...

  5. 为衣服添加NFC功能:挥下袖子就能安全支付,打开车门坐进去就能启动汽车|Nature子刊...

    丰色 发自 凹非寺 量子位 报道 | 公众号 QbitAI 用手机当车钥匙.用Apple Pay感应支付,大家是不是已经不觉得稀奇了? 但未来,完成这些操作可能连手机都不需要掏了: 直接挥一下袖子就可 ...

  6. Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

    中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...

  7. onenote快捷键_高效飞快地使用onenote快捷键:快捷键功能架构解析

    默认快捷键有近200组,涉及到的功能如此之多,但真正频繁使用的,可能也就几十组.如何从这么多快捷键中选择出自己需要的呢?你需要一张功能架构参考图. 1 默认快捷键功能架构图 官方文档已对快捷键做了初步 ...

  8. CentOS 5打造全功能Web服务器

    转:主要做为历史记录,以后用.另外很少见这么好的编译的文章,其实我不推荐用编译安装.但这个文章不错. V3.0 2007年11月11日 将搭建教程划分阶段以适应不同需要.加入程序优化,程序安全,加入m ...

  9. 用百数教培管理系统轻松实现在线排课,优化机构日常管理

    艺术类培训机构日常工作涉及的类目繁多,招生管理.课程安排.续费等等都需要产生许多信息数据.借助科学的管理系统,能够通过数据反映问题,进而优化工作方法,提升机构整体管理水平. 百数教培学校管理系统能够实 ...

最新文章

  1. c语言储存10的500,c语言程序基础练习题500道
  2. 操作系统-并发:死锁和饥饿
  3. 腾讯旗下网站的很多URL都包含“cgi-bin”,是什么意思?他们后台用什么语言?...
  4. rabbit以及php amqp扩展使用
  5. 加班其实不是真正的理由(Coders Life)
  6. man命令手册打开以后的使用方法
  7. 计算机与网络应用基础知识下上机考试,计算机应用基础知识考试
  8. H3C的DHCP中继配置命令
  9. bootstrap使用中遇到的坑
  10. SSM框架笔记11:Spring常用配置与高级话题
  11. oracle大于或等于,如何用SQL实现最接近某一个值且大于等于这个值的一条记录
  12. PostgreSQL Oracle 兼容性之 - rowid (CREATE TABLE WITH OIDS)
  13. 多级分类查询解决方案
  14. 论文写作: Abstract 和 Introduction 的 区别
  15. 如何教你获取1688店铺所有商品,商品详情数据分析
  16. Python中的re.search和re.group用法
  17. AWS知识图谱大赛之python数据处理和图数据库导入
  18. vue中怎么获取元素
  19. PAT甲级刷题记录-(AcWing)-(Day06树 8题)
  20. JAVA从菜鸟到架构师系列课程

热门文章

  1. WIN 系统怎么样查看EXpressCache功能
  2. Integrate k8s with cert-maanger and vault (by quqi99)
  3. 神经网络问答生成最全模型、策略、应用相关论文、资源、评测整理分享
  4. ORBSLAM2在Ubuntu20.04ROS noetic下配置与测试
  5. XMind 2021 for Mac v11.0.0 中文版 全新的思维导图软件
  6. 如何用数字化思维,变革传统供应链?
  7. 运行虚拟机系统,提示打不开DISK,启动失败的错误
  8. 抖音关注对方显示服务器打瞌睡了,抖音关注了对方又取消了有显示吗
  9. JS基础知识day8
  10. python token_token --- 与Python解析树一起使用的常量 — Python 3.8.6 文档