Open kun-song opened 6 years ago
Try
与 Monad
与 for 解析类似 Option
,我们可以为可能抛出异常的表达式定义如下容器:
sealed trait Try[+T]
final case class Success[T](v: T) extends Try[T]
final case class Failure(ex: Throwable) extends Try[Nothing]
/**
* 1. 我们一般这样使用 Try:Try(expression),即将可能抛出异常的表达式用 Try 包裹起来,这样看来,Try 应该类似一个函数,所以在 Try 伴生对象中
* 实现 apply 方法以支持 Try(expression) 的用法。
* 2. 注意 apply 参数必须是 lazy,否则 Try(expression) 计算时,会立即计算 expression 表达式,如果 expression 会抛出异常,
* 则 catch 子句无法捕获它。
*/
object Try {
def apply[T](f: => T): Try[T] =
try Success(f)
catch {
case NonFatal(ex) ⇒ Failure(ex)
}
}
我们也想在 for 解析中使用 Try
,毕竟 for 解析实在太方便了:
for {
x <- computeX
y <- computeY
} yield f(x, y)
computeX
和 computeY
都返回 Success
,则上面 for 解析返回 Success(f(x, y))
computeX
和 computeY
任意一个返回 Failure
,则上面的 for 解析返回 Failure(ex)
(注意:当两个都抛出异常时,ex 的选择与实现有关)使用 for 解析的理想很容易实现,只要为 Try
实现 flatMap
和 map
函数即可:
sealed trait Try[+T] { self ⇒
def flatMap[S](f: T ⇒ Try[S]): Try[S] =
self match {
case Success(v) ⇒
try f(v) match {
case NonFatal(ex) ⇒ Failure(ex)
}
case _ ⇒ _
}
def map[S](f: T ⇒ S): Try[S] =
self match {
case Success(v) ⇒ Try(f(v))
case _ ⇒ _
}
}
Monad
法则证明前面为 Try
实现了 flatMap
和 map
函数,但还不能说 Try
是 Monad
实例,因为还有 3 个 Monad
法则需要满足。
m flatMap f flatMap g
=
Try
机制?在 Java 中,如果一段代码可能抛出 checked exception,则编译器要求我们必须使用 try-catch
包住这段代码,我们已经很熟悉这种做法了,例如:
public class MainJ {
private double divide(double m, double n) throws Exception {
if (n == 0)
throw new Exception("divider can't be zero!");
return m / n;
}
private void test() {
double result = 0;
try {
result = divide(10, 1);
} catch (Exception e) {
// 处理
e.printStackTrace();
}
System.out.println(result);
}
}
因为 divide
声明会抛出 Exception
,而 Exception
属于 checked exception,所以任何使用 divide
方法的地方,都必须使用 try-catch
进行包裹,以捕获可能会抛出的异常。
写过 Java 的同学肯定对此非常熟悉,那么这种异常处理方式有什么问题吗?
try-catch
越来越多时,代码会越来越丑陋;目前我就想到这些缺点,但更重要是,在函数式编程中,非常强调引用透明,而 try-catch
恰恰破坏了引用透明,这一点是 Scala 无法忍受的,因为引用透明是是函数组合的基础,也是函数式编程的基础,所以 Scala 以 Try
来取代 try-catch
,以恢复引用透明。
如何判断表达式(或函数)是否引用透明呢? 很简单,如果一个表达式,或函数,可以被其计算结果取代,而对其他所有程序无任何应用,则它就是引用透明的。
try-catch
子句,在没有抛出异常时,也符合引用透明定义,但是如果抛出异常,则这段表达式 无任何返回值,并且产生了副作用(比如停止程序等),进而也就不是引用透明的了。
前面随机值生成器中定义了
flatMap
和map
函数,实际上,具备这两个函数的数据结构数不胜数,它们被统称为Monad
。Monad
是纯代数数据结构,在不同编程语言中,定义Monad
的方式不同,在 Scala 中,Monad
被定义为类型M[T]
,且:M
具备类型参数T
M
有两个基本函数:flatMap
和unit
,且它们满足一定的代数法则在 Scala 中,类型即可定义为
class
,也可定义为trait
,因为Monad
仅仅是类型的描述信息,并无具体实现,因此将其定义为特质:有了
Monad
的定义,很多 Scala 类型都可以对号入座,注意unit
函数类型为T => Monad[T]
:List
是Monad
,且其unit(x) = List(x)
(因为List(x)
类型为T => List[T]
)Set
是Monad
,且其unit(x) = Set(x)
(因为Set(x)
类型为T => Set[T]
)Option
也是Monad
,它的unit(x) = Some(x)
Generator
也是Monad
,它的unit(x) = single(x)
上面例子中的
List
Set
等虽然从名字上看,与Monad
并无半毛钱关系,但它们都满足Monad
的定义(类型参数 + 两个函数),所以他们都是Monad
类型的实例。注意上面的不同
Monad
实例中,unit
函数的名字各不相同(分别是List
Set
Some
single
),但是flatMap
函数的名字是完全相同的。现在
Monad
已经有了flatMap
,但是 for 解析还需要map
,实际上,对所有Monad
而言,map
都可以用unit
+flatMap
实现:注意:这里的
map
实现仅仅是示例,实际中,每个Monad
实例都有自己与众不同的unit
实现,因此,map
函数一般作为具体Monad
实例的 primitive combinator。Monad
法则目前
Monad
特质拥有unit
和flatMap
函数,这仅仅是形似 Monad,要变为真正的 Monad,这两个函数之间还需要满足 Monad 法则。结合律
Left Unit
Right Unit
Monad
法则实例Option
类型是Monad
实例,它必然满足Monad
法则:首先看下 left unit(
unit(x) flatMap f = f(x)
),在Option
中,Some
即为unit
,并且将flatMap
替换为Option.flatMap
实现,因此证明如下:Right unit 法则证明如下:
交换律证明如下:
Monad
法则与 for 解析