引入Option优雅地保证健壮性
REA的Ken Scambler在其演讲《2 Year of Real World FP at REA》中,总结了选择函数式编程的三个原因:Modularity, Abstraction和Composability。
函数式编程强调纯函数(Pure Function),这是模块化的一个重要基础,因为对于纯函数而言,可以不用考虑调用的上下文,就可以根据函数的输入推断函数的执行结果。这也就是Ken所谓的:
You can tell what it does without Looking at surrounding context.
Ken在演讲中给出了一个案例:
def parseLocation(str: String): Location = {
val parts = str.split(",")
val secondStr = parts(1)
val parts2 = secondStr.split(" ")
Location(parts(0), parts2(0), parts(1).toInt)}
仔细阅读这段代码,你会发现这段代码是不健壮的,可能存在如下错误:
- 作为input的str可能为null
- parts(0)和parts(1)可能导致索引越界
- parts2(0)可能导致索引越界
- parts(1)未必是整数,调用toInt可能导致类型转换异常
这段代码隐含的错误还可能被广泛地蔓延到系统的其他地方,只要该函数被调用。这种蔓延可能会因为更多嵌套的调用而产生级联的错误效应。例如:
def doSomethingElse(): Unit = {
// ...Do other stuff
parseLocation("Melbourne, VIC 3000")}
而doSomethingElse()
函数又被其他函数调用,这些潜在的缺陷会分布到各个直接或间接的调用点。这意味着代码会继承它所调用代码的错误以及副作用,使得对代码功能的推理(reasoning)变得近乎不可能,更不用说代码的模块化(modularity)了。
我们当然可以通过对null进行检测来避免出现这些错误。然而看看各种出现null值的可能分支,需要我们做各种条件判断,想象这样的代码都让人不寒而栗。引入Option类型就可以很好地封装这种可能性。按照Ken的说法就是:
All possibilities have been elevated into the type system.
def parseLocation(str: String): Option[Location] = {
val parts = str.split(",")
for {
locality <- parts.optGet(0)
theRestStr <- parts.optGet(1)
theRest = theRestStr.split(" ")
subdivision <- theRest.optGet(0)
postcodeStr <- theRest.optGet(1)
postcode <- postcodeStr.optToInt
} yield Location(locality, subdivision, postcode)}
以上代码中,split()
函数返回的类型为Array[String]
,该类型自身是没有optGet()
函数的。但是我们可以为Array[String]
定义隐式转换:
implicit class StringArrayWrapper(array: Array[String]) {
def optGet(index:Int): Option[String] = {
if (array.length > index) Some(array(index)) else None
}}
optToInt
方法可以如法炮制。
Ken的解决方案并没有考虑到parseLocation
函数入参str存在null值的可能,故而在对str调用split
方法时仍然有可能导致抛出空指针异常。因此进一步,我们还可以修改parseLocation
函数的定义:
def parseLocation(optStr: Option[String]): Option[Location]
显然,通过引入Option,既规避了前面分析可能出现的错误,又能避免编写繁琐的if判断。这里的关键点是Option对两种可能性(None与Some)的封装。它由两个代数类型Some与None构成,前者包含了一个值,而后者则包含了一个不存在的值。事实上,Option是一个Maybe Monad,实现了flatMap与filter,因而在Scala中可以用for comprehension来访问。
- python笔记2-冒泡排序
- Lucas定理学习(进阶中)
- Java8时间类使用方法
- Selenium2+python自动化46-js解决click失效问题
- PCA实现一个简单的酒店推荐系统(附Python源码)
- 【干货】动手实践:理解和优化GAN(附代码)
- Selenium2+python自动化44-元素定位参数化(find_element)
- Selenium2+python自动化45-18种定位方法(find_elements)
- Python做文本挖掘的情感极性分析(基于情感词典的方法)
- Selenium2+python自动化42-判断元素(expected_conditions)
- 基于机器学习的文本情感极性分析
- Selenium2+python自动化43-判断title(title_is)
- hihoCoder #1142 : 三分求极值
- 容斥原理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法