Scala的函数式编程
Scala的函数式编程
ForEleven 发表于4年前
Scala的函数式编程
  • 发表于 4年前
  • 阅读 8488
  • 收藏 19
  • 点赞 2
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

Scala的函数式编程

昨天去一家公司面试,额,他们想招erlang的开发人员,然后我会点scala,都是函数式语言么。所以就让我过去试试了,开始介绍了一些在做的项目架构什么的。最后开始跟我聊函数式编程,好吧,果断就不行了。当时开始学Scala的时候去看了下函数式编程的概念,当时也不懂,没有啥概念。只记得函数式编程不推荐使用变量,即使需要使用变量,也用不可变的变量。在Scala中也就是val定义的变量。其他的么那会就想不起来了,其实还有lambda表达式啊,这个已经知道的也没想起来。后来又让我在板子上写Partial Function,Curry Function和UnCurry Function。知道点Curry,Partial之前真的没见过。没有一点概念,回来还是得整理一下的,既然学了Scala,还是得深入了解一下函数式编程了。

Scala作为一个多范式编程语言,支持面向对象和函数式编程。虽然Scala不强求开发者使用函数式编程,不强求变量都是不可变的(通过val定义的),但是还是鼓励使用函数式编程。现在的计算机都是多核CPU,想充分利用其多核处理,我们需要写可并行计算的代码。而函数式编程在并行操作性有着天生的优势,函数式编程没有可变变量,那么就不会有内存共享的问题,也不会产生副作用(side effect)的函数。下面介绍函数式编程有哪些特性。

闭包和高阶函数

    在面向对象编程中,我们把对象作为编程中的第一类对象,所有代码的编写都是围绕对象来编程。那么函数式编程中,我们的第一类对象就是函数,也叫做 闭包 或者 仿函数 (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太高端,它还有很多高级的应用场景,等好好研究后,会专门介绍一下。

共有 人打赏支持
粉丝 205
博文 29
码字总数 28047
作品 1
×
ForEleven
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: