前言:函数式编程分为狭义和广义两个方面
狭义函数式编程,有着非常严格的标准,只通过纯函数编程,不允许有副作用,所有的数据结构都是不可以改变的。
广义的函数式编程,不强调的都是纯函数。常见的函数式的语言特性:
- 函数是头等公民
- 方便的闭包语法
- 递归式的构造列表
- 柯里化的函数
- 惰性求值
- 模式匹配
- 尾递归优化
- 强大的泛型能力,包括高阶类型(支持静态类型的函数式语言)
- Typeclass
- 类型推导。
Kotlin 支持上面大部分特性。
纯函数和引用透明性
sealed class Format
data class Print(val text: String) : Format()
object Newline : Format()
val string = listOf<Format>(Print("Hello"),
Newline, Print("Kotlin"))
fun unsafeInterpreter(str:List<Format>){
str.forEach {
when(it){
is Print -> print(it.text)
is Newline -> println()
}
}
}
unsafeInterpreter,引入了副作用
1.缺乏可测试性。如果变更,添加不是print而是写数据库相关方法,如果测试的时候,就会麻烦。
2.难易组合复用。内部混合了字符串格式转化逻辑,如果这里不是打印操作,而是数据库操作,就不能当做字符串转换功能使用。
稍微改动一下,举个例子说明:
fun stringInterpreter(str:List<Format>)
= str.fold(""){fullText,s ->
when(s){
is Print -> fullText + s.text
is Newline ->fullText + "\n"
}
}
函数返回字符串结果,无论测试还是复用,都有所提升
基本法则:引用透明性
一个表达式在程序中,可以被它等价的值替换,而不影响结果
一个函数具备引用透明性,内部行为不会改变外部状态
近似数学中的等式推理
纯函数与局部可变性
引用透明不是说不可变,而是局部可变,而整体调用外面是个黑盒,结果是一致的。
fun foo(x:Int):Int{
var y = 0
y = y + x
return y
}
foo具备纯函数f(x)->y.
副作用,在一定的抽象概念,并不绝对没有副作用,即使纯函数,也会使用内存,占用cpu
纯函数具有局限性random随机函数结果不一致都是不同的,不是纯函数
代换模型与惰性求值
错误的例子StackOverflowError 死循环
fun f1(x:Int,y:Int) = x
fun f2(x:Int):Int = f2(x)
- 应用序和正则序
kotlin中会先对f2(2) 进行求值,所以会导致死循环
然而如Hashell这种老的纯函数语言,会先调用f1(1,y),因为用不到y所以不会对f2(2)求值
- 惰性求值
模拟一下
比如针对println,他是一个非纯函数,改造让他"lazy"
fun lazyPrintln(msg:String) = { println(msg)}
当我们执行 lazyPrintln("I am IO operation"),
他仅仅返回一个可执行的println是惰性的,也是可以替代的。