闭包和高阶函数
在面向对象编程中,我们把对象作为编程中的第一类对象,所有代码的编写都是围绕对象来编程。那么函数式编程中,我们的第一类对象就是函数,也叫做
闭包
或者
仿函数
(functor)对象。而高阶函数的意思则是用另一个函数作为参数,甚至可以返回一个函数的叫做
高阶函数。
val double = (i: Int) => i * 2 //或者:val double
Int)=> Int = _ * 2 List(1, 2, 3, 4, 5).map(double).foreach{j => print(j + " ")}
>> 2 4 6 8 10
这里我们定义了一个double函数的变量,然后作为参数传给List的map方法,然后调用foreach方法打印出来。这里double就是一个仿函数对象,而map就是一个高阶函数。
无副作用
函数副作用维基百科的解释是:指當調用函數時,除了返回函數值之外,還對主調用函數產生附加的影響。因为函数式编程,都是函数变量或者不可变变量,所以不出出现函数副作用。但是,我们在程序设计的时候,肯定需要记录中间变量的,那么在函数式编程中,怎么来记录中间变量呢。
这是一个通过可变变量来保存每次记录值的写法来求x的n次方:
def expr(x:Int, n:Int):Int = { var result = 1 for(i <- 0 until n){ result = result*x } result }
那么无中间变量的写法呢
def expr2(x:Int,n:Int):Int={ if(n==0) 1 else x*expr2(x,n-1) }
;-( 肯爹呢,这是。这不就是递归么。哈哈,的确函数式编程中,中间变量只是通过递归来实现中间变量。
递归与尾递归
递归在函数式编程中地位不言而喻,它不仅解决了函数的副作用问题,还可以更加清晰的来写出复杂问题的处理函数。递归在函数式编程中有着大量的运用。
尾递归的维基百科的解释:尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。
在使用普通递归的时候,我们经常会见到stackoverflow的错误。而尾递归就是来解决这个问题,尾递归的每次调用的时候,不需要保存当前状态,而是把当前的状态值直接传给下次一次调用,然后清空当前的状态。那么占用的栈空间就是常量值了,不会出现栈的溢出。
下面是斐波那契数列计算的两种递归的写法
//递归 def fib(n:Int):Int = n match{ case 0 => 1 case 1 => 1 case _ =>fib(n-1) + fib(n-2) }
//尾递归 def fib2(a:Int,b:Int,n:Int):Int = n match{ case 0 => b case _ =>fib2(b,a+b,n-1) }
惰性计算
惰性
求值
特别用于
函数式编程语言
中。在使用延迟求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值
。
除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。
Scala中通过lazy关键字来定义惰性变量,惰性变量只能是不可变变量。例如下面,只有在调用惰性变量b的toString方法的时候,才会去实例化b这个变量。可以看到“Test”是先打印出来的。
class Book(name:String){ println("new book"+name) override def toString() = "《"+name+"》" } lazy val b = new Book("Java") println("Test") println(b.toString)
Test new bookJava 《Java》
引用透明性
引用透明(Referential Transparent)的概念与函数的副作用相关,且受其影响。 如果程序中两个相同值得表达式能在该程序的任何地方互相替换,而不影响程序的动作,那么该程序就具有引用透明性。它的优点是比非引用透明的语言的语义更容易理解,不那么晦涩。纯函数式语言没有变量,所以它们都具有引用透明性。
Scala中的函数
就像面向对象编程中,我们有反射机制来动态的改变对象。在函数式编程中,当然也可以动态的来修改函数。这里只是分别做了简单的介绍,后面有时间可以详细的介绍一下它们各自。
Partial Function
Partial Function局部方法,可以动态的将一个函数的任意位置的参数提供默认值,获得新的函数。新函数的参数个数从而发生改变。
比如我们有个函数,将字符b复制a次然后跟c拼接。
val f = (a: Int, b: String, c: String) => b * a + c
val f2 = f(5,_:String,_:String) // 为复制次数指定默认5次 println(f2("A","B")) >> AAAAAB
val f3 = f(_: Int, "A", _: String) // 为被复制的值指定默认为A println(f3(5, "B"))
Curry
比如有一个函数 f(a,b),通过Curry化以后的函数可以变成f1(a)(b),然后我们通过Partial函数f3= f1(10) _ 。这时候f3(2) == f(10,2)
//curry化 val curry = new Function3[Int,String,String,String](){ def apply(a: Int, b: String, c: String) = f(a,b,c) }
或者:val f6 = f.curried(10)(_:String)(_:String)
// 设定遍历次数默认值 val f4 = curry.curried(10)(_:String)(_:String) println(f4("A","B"))
Implicit
隐式函数(Implicit Function)的原理不太明白,它的运用场景就是可以动态给一个类添加一个方法,在Scala中甚至是JDK中定义的final类。下面,我们为java的String添加一个方法。
object Simple { implicit def wrapper(s:java.lang.String) =new {def wrap= "--" + s +"--"} } val s :java.lang.String = "Hello" println(s.wrap) >>> --Hello--
我们只需要在静态类中定义一个隐式方法,该方法的参数就是被隐式添加方法的类。当scala的编译器 编译 s.wrap 时,因为 String 类中没有 wrap方法,编译器会寻找代码中方法签名相同的 wrapper(s :String)。
Implicit Function太高端,它还有很多高级的应用场景,等好好研究后,会专门介绍一下。