从 OOP 程序员到 FP 程序员 . Monad 到底为程序设计带来啥好处 .
1. 计算电阻的例子
Programming F# 242 页有一个计算电阻的例子 , 是用 F# 的 computation expression 实现的 , 我会用 Scala 再实现一次 .
并列放置的电阻的阻抗满足关系 : 1/R= 1/R1 + 1/R2 + …+1/Rn. 为了简便起见 , 这里只考虑 3 个电阻的情况 .
2. 先写个计算函数 :
def calculateResistance0(r1: Float ,r2: Float ,r3: Float ) = {
val v1 = 1/r1
val v2 = 1/r2
val v3 = 1/r3
val v = 1/(v1+ v2+v3)
v
}
调用函数 :
val r = calculateResistance0(10, 20, 30) //return 5.4545455
当三个电阻中有一个 阻抗为零时 , 如 r1 =0, 1/r1 返回 Infinity 表示无穷大 . 那么 :
val r = calculateResistance0(0, 20, 30) // return 0.0
calculateResistance0 能处理各种情况 , 得到的结果没有问题 . 问题是 calculateResistance0 是最优的解吗 ? 为啥当 r1=0 时 , 我们还要去计算 1/r2, 1/r3 呢 ? 因为无穷大加任何数结果还是无穷大 , 无穷大的倒数一定是 0. 如果除法操作是一个很耗资源的操作 , 减少不必要的操作会提高 calculateResistance0 函数的效率
3. 改进 calculateResistance0:
def calculateResistance1(r1: Float ,r2: Float ,r3: Float ) = {
if (r1==0) 0 else
{ val v1 = 1/r1
if (r2 == 0) 0 else {
val v2 = 1/r2
if (r3==0) 0 else {
val v3 = 1/r3
val v = 1/(v1+v2+v3)
v
}
}
}
}
调用函数 :
val r = calculateResistance1(10, 20, 30) //return 5.4545455
val r = calculateResistance1(0, 20, 30) // return 0.0
结果正确 , 效率也提高了 .( 当 r3 为零而 r1,r2 不为零时 , 效率退化为 calculateResistance0). 但是 , 程序结构太丑陋了 . 严重影响观感 . 有没有办法让程序写得象 calculateResistance0 一样 , 效率象 calculateResistance1 一样呢 ?
4. 题外话
如果有同学说下面的代码效率最高 , 我同意 , 但它不是我要说明的主题 . 本文想要解决的问题是 , 再强调一遍 : 让程序写得象 calculateResistance0 一样 , 效率象 calculateResistance1 一样
def calculateResistance2(r1: Float ,r2: Float ,r3: Float ) = {
if (r1==0||r2==0||r3==0) 0.0
else
1/(1/r1+1/r2+1/r3)
}
5. Monad 闪亮登场
仿照 scala 的 Option 类写了个 M. 为了突出重点 , 剔除了没有关联的其他函数和 properties
abstract class M[+A]{
def get:A
def isEmpty: Boolean
def flatMap[B](f: A => M[B]):M[B] = {
if (isEmpty) Failure else f( this .get)
}
def map[B](f: A => B):M[B]= {
if (isEmpty) Failure else Success(f( this .get))
}
}
case object Failure extends M[ Nothing ]{
def isEmpty = true
def get = throw new NoSuchElementException( "None.get" )
}
case class Success[+A](x:A) extends M[A]{
def isEmpty = false
def get = x
}
M 代表一个 Monad.
再写一个除法 object:
object Div {
def apply(i: Float , j: Float ) = {
println( "called i=" +i+ ";j=" +j); // 监视是否调用本段代码
if (j==0) Failure else Success(i/j)}
}
Monad 的工作原理有时间再写 .
6. 重写 calculateResistance0
def calculateResistance3(r1: Float ,r2: Float ,r3: Float ) = {
for {
m1 <- Div(1,r1)
m2 <- Div(1,r2)
m3 <- Div(1,r3)
} yield (1/(m1+m2+m3))
}
调用函数 :
val r = calculateResistance3(10, 20, 30) //return Success(5.4545455)
val r = calculateResistance3(0, 20, 30) // return Failure
calculateResistance3 和 calculateResistance0 写法相似 , 效率和 calculateResistance1 一样 .
7. 结语
本文主要目的是说明用 Monad 来作什么 , 没有讨论 Monad 的工作原理 ( 网上有很多文章有介绍 ). 希望对大家理解 Monad 有帮助 .