天天看点

泛函编程(9)-异常处理-Option

    option是一种新的数据类型。形象的来描述:option就是一种特殊的list,都是把数据放在一个管子里;然后在管子内部对数据进行各种操作。所以option的数据操作与list很相似。不同的是option的管子内最多只能存放一个元素,在这个方面option的数据操作就比list简单的多,因为使用者不必理会数据元素的位置、顺序。option只有两种状态:包含一个任何类型的元素或者为空。或者这样讲:一个option实例包含 0 或 1 个元素;none代表为空,some(x)代表包含一个任意类型的元素x。和list的两种状态:nil及cons很是相似。值得注意的是,这个为空的概念与java的null值有根本的区别:none或nil值都具有明确的类型而null则可能是任何类型的数据。在java编程里我们通常需要单独附加一些程序来检查、处理null值,而none或nil代表了一个类型数据的状态,可以直接使用。

     既然option与list高度相似,让我们把list的数据类型设计搬过来试试:

这简直跟list一模样嘛。当然,结构是一样的,但因为option最多可以有一个元素,所有的操作函数将会简洁的多。

那么为什么要增加一种数据类型?option又是用来干什么的呢?

我们先拿个超简单的java例子来示范:

在写这段java程序时一个疑问立即跳了出来:如果出现了错误时这个函数该返回什么呢?函数申明divide返回double,但在发生运算错误后我们不能返回任何double值,任何double值都不正确。唯一选择就是通过异常处理(exception handling)来解决了。那是不是意味着这个函数的所有用户都必须自己增加一段代码去处理异常了呢?那么每个用户都必须这么写:

首先,不用再头疼该返回什么值了:出问题就直接返回none。不过使用者必须从option这个管子里先把值取出来,看起来好像又多了一道手续。实际上这就是oop和泛函编程概念之间的区别:泛函编程的风格就是在一些管子里进行数据读取,没有必要先取出来。看看如何使用以上函数吧:

简单明了许多吧。那下面我们就专注于这个option的实现吧。既然相像只有一个元素的list,那么就不需要哪些复杂的什么左右折叠算法了:

注意:上面的[b >: a]是指类型b是类型a的父类,结合+a变形,option[b]就是option[a]的父类:如果a是apple,那么b可以是fruit,那么上面的默认值类型就可以是fruit,或者是option[fruit]了。=> b表示输入参数b是拖延计算的,意思是在函数内部真正参考(refrence)这个参数时才会对它进行计算。

下面通过一些使用案例来说明:

option的内部函数组合例子:

option数据类型使编程者无须理会函数的异常,可以用简洁的语法专注进行函数组合(function composition)。普及使用option变成了泛函编程的重要风格。scala是一种jvm编程语言,因而在用scala编程时可能会调用大量的java库函数。那么我们如何保证在调用现有java库的同时又可以不影响泛函编程风格呢?我们需不需要在使用java函数时用null和exception而在scala中就用option呢?答案是否定的!通过泛函编程的函数组合我们可以在不改变java源代码的情况下实现对java库函数的“升格”(lifting)。实际上我们现在泛函编程中的风格要求是在调用某个函数时,这个函数要能接受option类型传入参数及返回option类型值。用函数类型来表达就是:把 a => b 这样的函数编程“升格”成 option[a] => option[b]这样的函数:

woo,简直太神奇了。先从类型匹配上分析:map(f) >>> option[b]。这个占位符 _ 在这里代表输入参数,就是 this >>>>>> opption[a]。所以类型匹配。实际上这个函数表达形式先明确了最后生成的结果函数是:给一个option,返回一个option,这不是典型的函数文本(lambda function)描述吗:oa => oa map f >>> _ map f 。

我们还是用上面那个简单的divide例子吧:divide(x,y)需要两个输入参数,我们可以再造个更简单的,一个输入参数的例子:9 除以任何double y:

就是一个简单的 a => b,我们可以试试使用:

传入一个double参数, 返回double值。

把divide9“升格”后再试试:

divide9升格成lifted, 传入lifted一个option, 返回一个option。正是我们期望的结果。

再试复杂一点的:两个、三个参数函数升格:

测试使用结果:

这显示了泛函编程函数组合的优雅但强大特性。

下面看看option的函数组合(function composition):map2用一个函数f在option管道内把两个option合并起来:

在实现了map和flatmap两个函数基础上,以上展示了for语法糖(syntatic sugar)的用法。

下面的例子是针对list里面的option,list[option[a]]来操作的。既然涉及到list,那么就可能涉及到折叠算法了。

下面这个例子:把list[option[a]]转化成option[list[a]],数据示范:list(some("hello"),some("world"))变成 some(list("hello","world")。一旦list里包含了none值则返回none:list(some("hello"),none,some("world"))直接变成none:

以上使用了map2:一个把两个option结合起来的函数。这次提供了一个创建list的操作函数。测试一下结果:

对于涉及list的情况,另外一个函数traverse也值得注意。下面是traverse的设计:

traverse的功能是使用函数f对list as里的所有元素进行作用,然后生成option[list[b]]。看看使用结果:

ok, option的介绍就到此了。