clojure-flavored-javascript icon indicating copy to clipboard operation
clojure-flavored-javascript copied to clipboard

monad与副作用,不纯函数

Open boboyada opened this issue 7 years ago • 3 comments

一、 有本书,是scala 函数编程,里面有个例子: 有两个玩家p1和p2, 写一个函数,把p1,p2中得分高的打印出来。如下: case class Player(name:String,score:Int) //这是一个Player的类,有两个属性,玩家姓名和得分

   def contest(p1:Player,p2:Player):Unit =     
		if(p1.score > p2.score)
			println("${p1.name} is the winner!")           //玩家1是赢家
			
			else if (p2.score >p1.score) 
			println("$(p2.name} is the winner!")           //玩家2是赢家
			
		else 
			println("It'a draw")                           //平局
			
上面的代码中,println函数是有副作用的,因为是要输出到IO里。

所以导致contest 这个方法是不纯的,有副作用,不容易测试。于是认为,需要把这个函数contest 进行修改,抽象出不纯的,变成一个由若干个纯函数组合模式。可以采用monad单子来抽象。 改进后的代码如下:

二、

case class Player(name:String,score:Int)

1.//trait类似于接口,只定义了一个方法,返回为空的意思。 描述有一个IO动作,至于这个动作什么时候执行,如何执行,则由外部来完成。 trait IO {def run:Unit}

2.纯函数,只返回赢家这个对象
def winner(p1:Player,p2:Player):Option[Player]= //Option有点类似于haskell中的Maybe if(p1.score >p2.score) Some(p1)
else if (p1.score<p2.score ) Some(p2) else None //平局

  1. //生成赢家所要打印出来的信息串 def winnerMsg(p:Option[player]) :Unit= p map {
    case Player(name,_) => "$name is the winner" //模式匹配,生成赢家的要打印的信息 } getOrElse "it's a draw //不是直接调用println 输出到屏幕

  2. //描述一个需要进行IO动作,具体的打印,则由系统的解释程序来完成 def PrintLine(msg:String) :IO=
    new IO {def run =println(msg)}

5.最终的组合函数 def contest(p1:Player,p2:Player):IO=
PrintLine(winnerMsg(winner(p1,p2)))

于是整个contest 函数都是纯的,无副作用的,而且是可以测试的。 但是这里没有看到与单子的影子呀? 单子不是用来剥离副作用的吗?怎么个剥离呢? 你怎么理解?

boboyada avatar Mar 13 '17 09:03 boboyada

因为您的书,使用了相当通俗而且可视化的方式来描述单子,所以有些困惑需要您的帮助才能更好的理解。我很喜欢您的这本书。 下面也是一篇讲monad与副作用的。 http://zhuoqiang.me/what-is-monad.html

在接触了monad,接触了纯函数,副作用后,有点似懂非懂,需要有一个东西,一个指导性的设计过程,在这个过程中,把这几个概念全部串起来,体现出这些东西来。 就好像上面的例子一样,打印玩家信息,变成了一个纯函数,把需要打印出来的玩家信息,封装在了一个io接口里,但是并不运行。 这就好像是redux-saga中yield call (action,paras),一样,call只是一个描述,描述有一个action要做,有什么参数,但是怎么做,则交给redux-saga的框架来完成。从而使得generator的内容可以被测试。 虽然我已经有点儿明白了,但此时也只是看到一个不纯的函数,是如何变成一个纯函数,可是最后一步的输出,在哪里?也没有看到单子在哪里?

boboyada avatar Mar 13 '17 10:03 boboyada

单子不是用来剥离副作用的吗?怎么个剥离呢?

是的

至于怎么剥,有很多种,有一个叫Free Monad的东西,Scala 的 cats有实现,让你剥所有特别的副作用,还有 Eff, scala也有 这种模式也叫interpreter模式,跟那个简单的例子一样,把业务逻辑中的纯的部分都抽象成free monad/DSL,再由带副作用的interpreter来解析,interpreter毫无逻辑,只有真正的IO操作 这时 interpreter不需要怎么测,因为没有逻辑,只要简单的 integration 测下IO就好 free monad是纯的,所以业务逻辑也非常好测

如果是一些通用的副作用,已经有现成剥好的monad可以直接用,比如Haskell里面的IO,不知道对应到scala是啥,还有什么Reader Writer State monad

jcouyang avatar Mar 13 '17 13:03 jcouyang

winnerMsg(p:Option[player]) :Unit 这个返回值应该是String类型吧

这个例子确实没有Monad,应该只是介绍如何分离纯与不纯的部分 如果硬要说用monad抽象了什么,用Option封装了if-else逻辑 IO的意思应该只是类似interpreter,也就是不纯的部分

jcouyang avatar Mar 13 '17 13:03 jcouyang