《FP In Scala》笔记
纯函数
- 是什么
- 一个函数在程序执行的过程中除了根据输入参数给出运算结果之外没有其他的影响。
- 举例
intToString()
函数只负责将Int
转换为String
,除此之外不应该做任何事情。+
,接收两个可加数,返回一个结果。
引用透明
-
是什么
- 表达式可以被它引用的值所代替
-
作用
- 惰性求值
- 并行处理
-
举例
- 生成随机数
函数式编程的好处
- 没有副作用:不修改外部的值
- 引用透明:不依赖外部的状态
函数式编程和面向对象编程
- 我来终结此贴。程序的本质就是一头有输入,而另一头有输出,但关键是从输入到输出的这个映射体十分复杂。面向对象的方法是将处理职责拆分到不同的类,然后组合和复用这些类来构建程序,但如何拆分和如何给这些细小部分的处理职责定个类名?没有标准答案,这也是OO系统混乱的根源,所以早期为了解决系统混乱,软件工程告诉我们要从业务角度去拆分,所以软工必修UML,用于系统分析设计,但人们抛弃了UML,说我们要敏捷!先定义表再定义类,一大堆getter、setter,再写上一堆mapper的废物注解,然后喊我们是面向对象。现在2020年了,依旧如此。另一方面,函数式的做法就更加贴近计算与组合的本质,它将这个复杂的映射体拆分成小的映射体,这些映射体都以计算来命名,比如一个toUpperCase function,我明确知道这是一个我给它一个字符串,它还我一个将所有英文字符转为大写的字符串,函数式编程就是通过组合这些细小的映射体来构建复杂的映射体,所以组合才是functional programming的本质,这时候会有人喊Monad?范畴论?扯皮!写FP根本不需要理解范畴论,Monad的存在,是因为我们输入与输出有时候是不定的、不具体的,它们需要修饰词,比如null处理,异常处理,所以才会有Maybe和Either。什么?纯度处理?IO?魔怔?追求纯粹?在计算机世界里,最初的输入与最终的输出不可能是纯的!副作用总是真实存在。我们说的复杂是指构建这整个大的映射体的逻辑组合的复杂,而不是去纠结最初和最终到底是不是纯!
使用Option代替异常
-
代码
def parseInt(str: String): Option[Int] = { Try(str.toInt).toOption } def testOption(str: String): Unit = { val maybeInt: Option[Int] = parseInt(str) maybeInt match { case Some(x) => println("\"" + x + "\"" + " is a number") case None => println("\"" + str + "\"" + " is not a number") } } def main(args: Array[String]): Unit = { testOption("Hello world") testOption("12345") }
尾调用
-
是什么
- 调用者在递归调用之后不做任何事情
-
举例
-
loop函数就是一个尾调用
def testTail(n: Int): Int = { @tailrec def loop(res: Int, acc: Int): Int = { if (acc <= 0) { res } else { loop(res * acc, acc - 1) } } loop(1, n) }
-
匿名函数
-
本质
-
有
apply()
方法方法的对象就能直接被调用,比如f1
就是一个函数字面量,值为(a: Int, b: Int) => a < b
,f1
会被编译器转换为f2
的形式,Function2
是一个特质,表示apply()
方法具有两个参数,即方法签名为apply(Int, Int)
val f1 = (a: Int, b: Int) => a < b val f2 = new Function2[Int, Int, Boolean] { def apply(a: Int, b: Int): Boolean = a < b }
-