Scalaz(13)- Monad:Writer - some kind of logger

2024-04-09 05:08
文章标签 13 logger monad writer kind scalaz

本文主要是介绍Scalaz(13)- Monad:Writer - some kind of logger,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  通过前面的几篇讨论我们了解到F[T]就是FP中运算的表达形式(representation of computation)。在这里F[]不仅仅是一种高阶类型,它还代表了一种运算协议(computation protocol)或者称为运算模型好点,如IO[T],Option[T]。运算模型规范了运算值T的运算方式。而Monad是一种特殊的FP运算模型M[A],它是一种持续运算模式。通过flatMap作为链条把前后两个运算连接起来。多个flatMap同时作用可以形成一个程序运行链。我们可以在flatMap函数实现中增加一些附加作用,如维护状态值(state value)、跟踪记录(log)等。 

  在上一篇讨论中我们用一个Logger的实现例子示范了如何在flatMap函数实现过程中增加附加作用;一个跟踪功能(logging),我们在F[T]运算结构中增加了一个String类型值作为跟踪记录(log)。在本篇讨论中我们首先会对上篇的Logger例子进行一些log类型的概括,设计一个新的Logger结构:

case class Logger[LOG, A](log: LOG, value: A) {def map[B](f: A => B): Logger[LOG,B] = Logger(log, f(value))def flatMap[B](f: A => Logger[LOG,B])(implicit M: Monoid[LOG]): Logger[LOG,B] = {val nxLogger = f(value)Logger(log |+| nxLogger.log, nxLogger.value)}}

以上Logger对LOG类型进行了概括:任何拥有Monoid实例的类型都可以,能够支持Monoid |+|操作符号。这点从flatMap函数的实现可以证实。

当然我们必须获取Logger的Monad实例才能使用for-comprehension。不过由于Logger有两个类型参数Logger[LOG,A],我们必须用type lambda把LOG类型固定下来,让Monad运算只针对A类型值:

object Logger {implicit def toLogger[LOG](implicit M: Monoid[LOG]) = new Monad[({type L[x] = Logger[LOG,x]})#L] {def point[A](a: => A) = Logger(M.zero,a)def bind[A,B](la: Logger[LOG,A])(f: A => Logger[LOG,B]): Logger[LOG,B] = la flatMap f}
}

有了Monad实例我们可以使用for-comprehension:

def enterInt(x: Int): Logger[String, Int] = Logger("Entered Int:"+x, x)//> enterInt: (x: Int)Exercises.logger.Logger[String,Int]
def enterStr(x: String): Logger[String, String] = Logger("Entered String:"+x, x)//> enterStr: (x: String)Exercises.logger.Logger[String,String]for {a <- enterInt(3)b <- enterInt(4)c <- enterStr("Result:")
} yield c + (a * b).shows                         //> res0: Exercises.logger.Logger[String,String] = Logger(Entered Int:3Entered I//| nt:4Entered String:Result:,Result:12)


不过必须对每个类型定义操作函数,用起来挺不方便的。我们可以为任何类型注入操作方法:

final class LoggerOps[A](a: A) {def applyLog[LOG](log: LOG): Logger[LOG,A] = Logger(log,a)
}
implicit def toLoggerOps[A](a: A) = new LoggerOps[A](a)//> toLoggerOps: [A](a: A)Exercises.logger.LoggerOps[A]

我们为任意类型A注入了applyLog方法:

3.applyLog("Int three")                           //> res1: Exercises.logger.Logger[String,Int] = Logger(Int three,3)
"hi" applyLog "say hi"                            //> res2: Exercises.logger.Logger[String,String] = Logger(say hi,hi)
for {a <- 3 applyLog "Entered Int 3"b <- 4 applyLog "Entered Int 4"c <- "Result:" applyLog "Entered String 'Result'"
} yield c + (a * b).shows                         //> res3: Exercises.logger.Logger[String,String] = Logger(Entered Int 3Entered //| Int 4Entered String 'Result',Result:12)

用aplyLog这样操作方便多了。由于LOG可以是任何拥有Monoid实例的类型。除了String类型之外,我们还可以用Vector或List这样的高阶类型:

for {a <- 3 applyLog Vector("Entered Int 3")b <- 4 applyLog Vector("Entered Int 4")c <- "Result:" applyLog Vector("Entered String 'Result'")
} yield c + (a * b).shows                         //> res4: Exercises.logger.Logger[scala.collection.immutable.Vector[String],Str//| ing] = Logger(Vector(Entered Int 3, Entered Int 4, Entered String 'Result')//| ,Result:12)

一般来讲,用Vector效率更高,在下面我们会证实这点。

既然A可以是任何类型,那么高阶类型如Option[T]又怎样呢:

for {oa <- 3.some applyLog Vector("Entered Some(3)")ob <- 4.some applyLog Vector("Entered Some(4)")
} yield ^(oa,ob){_ * _}                           //> res0: Exercises.logger.Logger[scala.collection.immutable.Vector[String],Opti//| on[Int]] = Logger(Vector(Entered Some(3), Entered Some(4)),Some(12))

一样可以使用。注意oa,ob是Option类型所以必须使用^(oa,ob){...}来结合它们。

我们再来看看Logger的典型应用:一个gcd(greatest common denominator)算法例子:

def gcd(x: Int, y: Int): Logger[Vector[String], Int] = {if (y == 0 ) for {_ <- x applyLog Vector("Finished at " + x)} yield xelsex applyLog Vector(x.shows + " mod " + y.shows + " = " + (x % y).shows) >>= {_ => gcd(y, x % y) }}                                                 //> gcd: (x: Int, y: Int)Exercises.logger.Logger[Vector[String],Int]
gcd(18,6)                                         //> res5: Exercises.logger.Logger[Vector[String],Int] = Logger(Vector(18 mod 6 //| = 0, Finished at 6),6)
gcd(8,3)                                          //> res6: Exercises.logger.Logger[Vector[String],Int] = Logger(Vector(8 mod 3 =//|  2, 3 mod 2 = 1, 2 mod 1 = 0, Finished at 1),1)


注意 >>= 符号的使用,显现了Logger的Monad实例特性。

实际上scalar提供了Writer数据结构,它是WriterT类型的一个特例:

type Writer[+W, +A] = WriterT[Id, W, A]

我们再看看WriterT:scalaz/WriterT.scala

final case class WriterT[F[_], W, A](run: F[(W, A)]) { self =>
...

WriterT在运算值A之外增加了状态值W,形成一个对值(paired value)。这是一种典型的FP状态维护模式。不过WriterT的这个(W,A)是在运算模型F[]内的。这样可以实现更高层次的概括,为这种状态维护的运算增加多一层运算协议(F[])影响。我们看到Writer运算是WriterT运算模式的一个特例,它直接计算运算值,不需要F[]影响,所以Writer的F[]采用了Id,因为Id[A] = A。我们看看WriterT是如何通过flatMap来实现状态维护的:scalaz/WriterT.scala:

 def flatMap[B](f: A => WriterT[F, W, B])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] =flatMapF(f.andThen(_.run))def flatMapF[B](f: A => F[(W, B)])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] =writerT(F.bind(run){wa =>val z = f(wa._2)F.map(z)(wb => (s.append(wa._1, wb._1), wb._2))})

在flatMapF函数里对(W,A)的W进行了Monoid append操作。

实际上Writer可以说是一种附加的数据结构,它在运算模型F[A]内增加了一个状态值W形成了F(W,A)这种形式。当我们为任何类型A提供注入方法来构建这个Writer结构后,任意类型的运算都可以使用Writer来实现在运算过程中增加附加作用如维护状态、logging等等。我们看看scalaz/Syntax/WriterOps.scala:

package scalaz
package syntaxfinal class WriterOps[A](self: A) {def set[W](w: W): Writer[W, A] = WriterT.writer(w -> self)def tell: Writer[A, Unit] = WriterT.tell(self)
}trait ToWriterOps {implicit def ToWriterOps[A](a: A) = new WriterOps(a)
}


存粹是方法注入。现在任何类型A都可以使用set和tell来构建Writer类型了:

3 set Vector("Entered Int 3")                     //> res2: scalaz.Writer[scala.collection.immutable.Vector[String],Int] = WriterT//| ((Vector(Entered Int 3),3))
"hi" set Vector("say hi")                         //> res3: scalaz.Writer[scala.collection.immutable.Vector[String],String] = Writ//| erT((Vector(say hi),hi))
List(1,2,3) set Vector("list 123")                //> res4: scalaz.Writer[scala.collection.immutable.Vector[String],List[Int]] = W//| riterT((Vector(list 123),List(1, 2, 3)))
3.some set List("some 3")                         //> res5: scalaz.Writer[List[String],Option[Int]] = WriterT((List(some 3),Some(3//| )))
Vector("just say hi").tell                        //> res6: scalaz.Writer[scala.collection.immutable.Vector[String],Unit] = Writer//| T((Vector(just say hi),()))


用Writer运算上面Logger的例子:

for {a <- 3 set "Entered Int 3 "b <- 4 set "Entered Int 4 "c <- "Result:" set "Entered String 'Result'"
} yield c + (a * b).shows                         //> res7: scalaz.WriterT[scalaz.Id.Id,String,String] = WriterT((Entered Int 3 En//| tered Int 4 Entered String 'Result',Result:12))

如果A是高阶类型如List[T]的话,还能使用吗:

for {la <- List(1,2,3) set Vector("Entered List(1,2,3)")lb <- List(4,5) set Vector("Entered List(4,5)")lc <- List(6) set Vector("Entered List(6)")
} yield (la |@| lb |@| lc) {_ + _ + _}            //> res1: scalaz.WriterT[scalaz.Id.Id,scala.collection.immutable.Vector[String]//| ,List[Int]] = WriterT((Vector(Entered List(1,2,3), Entered List(4,5), Enter//| ed List(6)),List(11, 12, 12, 13, 13, 14)))

的确没有问题。

那个gcd例子还是挺有代表性的,我们用Writer来运算和跟踪gcd运算:

def gcd(a: Int, b: Int): Writer[Vector[String],Int] =if (b == 0 ) for {_ <- Vector("Finished at "+a.shows).tell} yield aelseVector(a.shows+" mod "+b.shows+" = "+(a % b).shows).tell >>= {_ => gcd(b,a % b)}//> gcd: (a: Int, b: Int)scalaz.Writer[Vector[String],Int]gcd(8,3)                                          //> res8: scalaz.Writer[Vector[String],Int] = WriterT((Vector(8 mod 3 = 2, 3 mo//| d 2 = 1, 2 mod 1 = 0, Finished at 1),1))
gcd(16,4)                                         //> res9: scalaz.Writer[Vector[String],Int] = WriterT((Vector(16 mod 4 = 0, Fin//| ished at 4),4))

在维护跟踪记录(logging)时使用Vector会比List更高效。我们来证明一下:

def listLogCount(c: Int): Writer[List[String],Unit] = {@annotation.tailrecdef countDown(c: Int, w: Writer[List[String],Unit]): Writer[List[String],Unit] = c match {case 0 => w >>= {_ => List("0").tell }case x => countDown(x-1, w >>= {_ => List(x.shows).tell })}val t0 = System.currentTimeMillisval r = countDown(c,List[String]().tell)val t1 = System.currentTimeMillisr >>= {_ => List((t1 -t0).shows+"msec").tell }
}                                                 //> listLogCount: (c: Int)scalaz.Writer[List[String],Unit]
def vectorLogCount(c: Int): Writer[Vector[String],Unit] = {@annotation.tailrecdef countDown(c: Int, w: Writer[Vector[String],Unit]): Writer[Vector[String],Unit] = c match {case 0 => w >>= {_ => Vector("0").tell }case x => countDown(x-1, w >>= {_ => Vector(x.shows).tell })}val t0 = System.currentTimeMillisval r = countDown(c,Vector[String]().tell)val t1 = System.currentTimeMillisr >>= {_ => Vector((t1 -t0).shows+"msec").tell }
}                                                 //> vectorLogCount: (c: Int)scalaz.Writer[Vector[String],Unit](listLogCount(10000).run)._1.last                 //> res10: String = 361msec
(vectorLogCount(10000).run)._1.last               //> res11: String = 49msec


看,listLogCount(10000)用了361msec

vectorLogCount(10000)只用了49msec,快了8,9倍呢。



这篇关于Scalaz(13)- Monad:Writer - some kind of logger的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887251

相关文章

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

13 transition数组的动画使用

划重点 动画:transitiontransition-group :数组动画数组的 添加 / 删除 豆腐粉丝汤 清淡又健康 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><me

【CTF Web】BUUCTF Upload-Labs-Linux Pass-13 Writeup(文件上传+PHP+文件包含漏洞+PNG图片马)

Upload-Labs-Linux 1 点击部署靶机。 简介 upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。 注意 1.每一关没有固定的通关方法,大家不要自限思维! 2.本项目提供的writeup只是起一个参考作用,希望大家可以分享出自己的通关思路

Chapter 13 普通组件的注册使用

欢迎大家订阅【Vue2+Vue3】入门到实践 专栏,开启你的 Vue 学习之旅! 文章目录 前言一、组件创建二、局部注册三、全局注册 前言 在 Vue.js 中,组件是构建应用程序的基本单元。本章详细讲解了注册和使用 Vue 的普通组件的两种方式:局部注册和全局注册。 本篇文章参考黑马程序员 一、组件创建 ①定义 Vue 组件是一种具有特定功能的 Vue 实

VMware Fusion Pro 13 Mac版虚拟机 安装Win11系统教程

Mac分享吧 文章目录 Win11安装完成,软件打开效果一、VMware安装Windows11虚拟机1️⃣:准备镜像2️⃣:创建虚拟机3️⃣:虚拟机设置4️⃣:安装虚拟机5️⃣:解决连不上网问题 安装完成!!! Win11安装完成,软件打开效果 一、VMware安装Windows11虚拟机 首先确保自己的mac开启了网络共享。不然虚拟机连不上👀的 1️⃣:准备镜像

华为 HCIP-Datacom H12-821 题库 (13)

有需要题库的可以看主页置顶 1.可以携带外部路由的 tag 标签信息的是以下哪一类 LSA? A、4 类 LSA B、5 类 LSA  C、3 类 LSA  D、2 类 LSA 答案:B 解析: 暂无解析 2..两台路由器直连,并设定网络类型为 p2p 建立OSPF 邻居。那么两台路由器传输 OSPF 报文的目的 IP 地址是以下哪一项? A、使用组播地址 224.0.0.6 B

[情商-13]:语言的艺术:何为真实和真相,所谓真相,就是别人想让你知道的真相!洞察谎言与真相!

目录 前言: 一、说话的真实程度分级 二、说谎动机分级:善意谎言、中性谎言、恶意谎言 三、小心:所谓真相:只说对自己有利的真相 四、小心:所谓真相:就是别人想让你知道的真相 五、小心:所谓善解人意:就是别人只说你想要听到的话 前言: 何为真实和真相,所谓真相,就是别人想让你知道的真相!洞察谎言与真相! 人与人交流话语中,处处充满了不真实,完全真实的只是其中一小部分,这

C++笔试强训12、13、14

文章目录 笔试强训12一、选择题1-5题6-10题 二、编程题题目一题目二 笔试强训13一、选择题1-5题6-10题 二、编程题题目一题目二 笔试强训14一、选择题1-5题6-10题 二、编程题题目一题目二 笔试强训12 一、选择题 1-5题 引用:是一个别名,与其被引用的实体公用一份内存空间,编译器不会给引用变量单独开辟新的空间。A错误 故选A。 A

java基础总结13-面向对象9(对象转型)

对象转型分为两种:一种叫向上转型(父类对象的引用或者叫基类对象的引用指向子类对象,这就是向上转型),另一种叫向下转型。转型的意思是:如把float类型转成int类型,把double类型转成float类型,把long类型转成int类型,这些都叫转型。把一种形式转成另外一种形式就叫转型。除了基础数据类型的转型之外(基础数据类型的转型:大的可以转成小的,小的也可以转成大的。),对象领域里面也有对象之

【MyBatis学习13】MyBatis中的二级缓存

1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的。为了更加清楚的描述二级缓存,先来看一个示意图: 从图中可以看出: 1.sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到该UserMapper的二级缓存中。2.