Scala-函數(shù)式編程

Scala-函數(shù)式編程

1.函數(shù)式編程

1.1 面向對象和面向過程

面向對象

  • 按照功能劃分問題碍沐,將功能分解成不同的的對象,定義對象中的行為和屬性,最終通過對象的行為調用來解決問題南蹂。
  • 優(yōu)點:
    • 耦合度低。
  • 缺點:
    • 執(zhí)行效率低念恍。

面向過程

  • 按照步驟解決問題六剥。
  • 優(yōu)點:
    • 執(zhí)行效率高。
  • 缺點:
    • 耦合度高峰伙。

1.2 函數(shù)式編程

函數(shù)式編程和命令式編程的區(qū)別:

  • 其實面向對象和面向過程都是命令式編程(計算機的命令)疗疟,其實語言其實都是面向計算機硬件的抽象。
    • int a=1瞳氓;這里a只是內存地址的引用策彤,還可以讓a=其他數(shù),是個變量匣摘。
    • 命令式編程的編碼都可以翻譯成計算機底層的指令店诗,對計算機最友好的語言,是計算機的子程序音榜。
  • 而函數(shù)式編程則重點關注的是<font color=red>數(shù)據(jù)的映射關系</font>(自變量因變量的映射關系)庞瘸,<font color=red> 不關心計算機底層如何實現(xiàn)</font>,這里的函數(shù)指的是數(shù)學中的函數(shù)赠叼。
    • val a =1 ,函數(shù)式編程中其實沒有變量擦囊,即一個常量违霞,a=1不能再讓a=其他數(shù),這更符合數(shù)學中函數(shù)的定義瞬场。
    • sacla中因為其有函數(shù)式編程的特點买鸽,所以推薦能用常量的不要用變量。
    • 定義一個函數(shù)贯被,求未知數(shù)的值眼五,函數(shù)式編程中每一段程序都有一個返回值。
    • 函數(shù)式編程本質就是數(shù)據(jù)的映射關系刃榨,定義一個表達式弹砚,通過該表達式不同的求值双仍,做函數(shù)的映射關系枢希。
    • 函數(shù)至簡原則:能省則省 ,只專注于對應的業(yè)務代碼朱沃。
      • java1.8中的函數(shù)式編程就是參照于此苞轿,簡化和業(yè)務無關的邏輯。

兩種編程范式的優(yōu)缺點:

  • 命令式編程:
    • 是對于計算機有好的編程逗物,因為都是對應計算機命令的編程搬卒。
    • 因為最終都是翻譯成計算機命令,所以其受計算機環(huán)境的影響很大翎卓。
  • 函數(shù)式編程:
    • 是對于人更好理解的契邀,定義函數(shù)的理念和數(shù)學上一致,不用關心計算機底層失暴。
    • 定義一個函數(shù)坯门,那么其功能就確定了,該公式是不可變的逗扒,入?yún)⒒蛘哒f未知數(shù)可以來回變古戴,但是同一個值對應的結果一定是唯一的。
    • 天然適用于分布式計算:
      • <font color=red>函數(shù)式編程中沒有變量矩肩,都是常量现恼,處理邏輯的過程中具有不可變性,不受外界影響</font>黍檩,天然適合在不同的機器上運行叉袍,最終只需要將各個機器上的值匯總即可,特別的適合大數(shù)據(jù)處理的分布式計算刽酱。

scala兼容了兩種編程范式的特點喳逛,scala是一個面向對象的語言,同時也是一個面向編程的語言肛跌。

2. 函數(shù)基礎

2.1 函數(shù)基本語法

image.png

2.1.1 例子

object TestFunction {
    def main(args: Array[String]): Unit = {
        // (1)函數(shù)定義
        def f(arg: String): Unit = {
            println(arg)
        }
        // (2)函數(shù)調用
        // 函數(shù)名(參數(shù))
        f("hello world")
    }
}

2.2 函數(shù)和方法的區(qū)別

java中的方法強調的是:

  • 類中的函數(shù)艺配,類中定義的位置是有限制的察郁。
    • 比如就不能在方法中定義方法。
  • 方法可以進行重載和重寫转唉。

scala中的函數(shù)

  • 定義在類中的任意代碼塊中皮钠。
  • Scala 語言可以在任何的語法結構中聲明任何的語法。
  • 函數(shù)沒有重載和重寫的概念赠法。
  • Scala 中函數(shù)可以嵌套定義麦轰。

2.2.1 例子

object FunctionAndMethod {
  def main(args: Array[String]): Unit = {
    // 定義函數(shù)
    def sayHi(name: String): Unit = {
      println("hi, " + name)
    }

    // 調用函數(shù),根據(jù)函數(shù)的作用域,按照就近原則砖织,先調用main中定義的sayHi函數(shù)
    sayHi("alice")

    // 調用對象方法款侵,為了調用方法可以如下調用
    FunctionAndMethod.sayHi("bob")

    // 獲取方法返回值
    val result = FunctionAndMethod.sayHello("cary")
    println(result)
  }

  // 定義對象的方法
  def sayHi(name: String): Unit = {
    println("Hi, " + name)
  }

  def sayHello(name: String): String = {
    println("Hello, " + name)
    return "Hello"
  }
}

2.3 函數(shù)的定義

  • 函數(shù) 1:無參,無返回值
  • 函數(shù) 2:無參侧纯,有返回值
  • 函數(shù) 3:有參新锈,無返回值
  • 函數(shù) 4:有參,有返回值
  • 函數(shù) 5:多參眶熬,無返回值
  • 函數(shù) 6:多參妹笆,有返回值

2.3.1 例子

object FunctionDefine {
  def main(args: Array[String]): Unit = {
    //    (1)函數(shù)1:無參,無返回值
    def f1(): Unit = {
      println("1. 無參娜氏,無返回值")
    }
    f1()
    println(f1())

    println("=========================")

    //    (2)函數(shù)2:無參拳缠,有返回值
    def f2(): Int = {
      println("2. 無參,有返回值")
      return 12
    }
    println(f2())

    println("=========================")

    //    (3)函數(shù)3:有參贸弥,無返回值
    def f3(name: String): Unit = {
      println("3:有參窟坐,無返回值 " + name)
    }

    println(f3("alice"))

    println("=========================")

    //    (4)函數(shù)4:有參,有返回值
    def f4(name: String): String = {
      println("4:有參绵疲,有返回值 " + name)
      return "hi, " + name
    }

    println(f4("alice"))

    println("=========================")

    //    (5)函數(shù)5:多參哲鸳,無返回值
    def f5(name1: String, name2: String): Unit = {
      println("5:多參,無返回值")
      println(s"${name1}和${name2}都是我的好朋友")
    }

    f5("alice","bob")

    println("=========================")

    //    (6)函數(shù)6:多參最岗,有返回值
    def f6(a: Int, b: Int): Int = {
      println("6:多參帕胆,有返回值")
      return a + b
    }

    println(f6(12, 37))
  }
}

2.4 函數(shù)參數(shù)

  • (1)可變參數(shù) (不確定個參數(shù)傳入)
  • (2)如果參數(shù)列表中存在多個參數(shù),那么可變參數(shù)一般放置在最后
  • (3)參數(shù)默認值般渡,一般將有默認值的參數(shù)放置在參數(shù)列表的后面
  • (4)帶名參數(shù)(入?yún)⒖梢赃x擇性的指定某個參數(shù)進行傳值)

2.4.1 例子

object Test03_FunctionParameter {
  def main(args: Array[String]): Unit = {
    //    (1)可變參數(shù),其實此時入?yún)⒁呀?jīng)是集合類型了懒豹,這里是數(shù)組
    def f1(str: String*): Unit = {
      println(str)
    }

    f1("alice")
    f1("aaa", "bbb", "ccc")

    //    (2)如果參數(shù)列表中存在多個參數(shù),那么可變參數(shù)一般放置在最后
    def f2(str1: String, str2: String*): Unit = {
      println("str1: " + str1 + " str2: " + str2)
    }
    f2("alice")
    f2("aaa", "bbb", "ccc")

    //    (3)參數(shù)默認值驯用,一般將有默認值的參數(shù)放置在參數(shù)列表的后面
    def f3(name: String = "xxx"): Unit = {
      println("My school is " + name)
    }

    f3("school")
    f3()

    //    (4)帶名參數(shù)
    def f4(name: String = "xx", age: Int): Unit = {
      println(s"${age}歲的${name}在學習")
    }

    f4("alice", 20)
    f4(age = 23, name = "bob")
    f4(age = 21)
  }
}

輸出

WrappedArray(alice)
WrappedArray(aaa, bbb, ccc)

str1: alice str2: WrappedArray()
str1: aaa str2: WrappedArray(bbb, ccc)

My school is school
My school is xxx

20歲的alice在學習
23歲的bob在學習
21歲的xx在學習

2.5 函數(shù)至簡原則

函數(shù)至簡原則:能省則省 脸秽,只專注于對應的業(yè)務代碼。

2.5.1 至簡原則細節(jié)

  • (1)return 可以省略蝴乔,Scala 會使用函數(shù)體的最后一行代碼作為返回值
  • (2)如果函數(shù)體只有一行代碼记餐,可以省略花括號
  • (3)返回值類型如果能夠推斷出來,那么可以省略(:和返回值類型一起省略)
  • (4)如果有 return薇正,則不能省略返回值類型片酝,必須指定
  • (5)如果函數(shù)明確聲明 unit囚衔,那么即使函數(shù)體中使用 return 關鍵字也不起作用
  • (6)Scala 如果期望是無返回值類型,可以省略等號
  • (7)如果函數(shù)無參雕沿,但是聲明了參數(shù)列表练湿,那么調用時,小括號审轮,可加可不加
  • (8)如果函數(shù)沒有參數(shù)列表肥哎,那么小括號可以省略,調用時小括號必須省略
  • (9)如果不關心名稱疾渣,只關心邏輯處理篡诽,那么函數(shù)名(def)可以省略

2.5.2 例子

object Simplify {
  def main(args: Array[String]): Unit = {

    def f0(name: String): String = {
      return name
    }

    println(f0("xxx"))

    println("==========================")

    //    (1)return可以省略,Scala會使用函數(shù)體的最后一行代碼作為返回值
    def f1(name: String): String = {
      name
    }
    println(f1("xxx"))

    println("==========================")

    //    (2)如果函數(shù)體只有一行代碼榴捡,可以省略花括號
    def f2(name: String): String = name
    println(f2("xxx"))

    println("==========================")

    //    (3)返回值類型如果能夠推斷出來杈女,那么可以省略(:和返回值類型一起省略)
    def f3(name: String) = name
    println(f3("xxx"))

    println("==========================")

    //    (4)如果有return,則不能省略返回值類型薄疚,必須指定
//    def f4(name: String) = {
//      return name
//    }
//
//    println(f4("xxx"))

    println("==========================")

    //    (5)如果函數(shù)明確聲明unit碧信,那么即使函數(shù)體中使用return關鍵字也不起作用
    def f5(name: String): Unit = {
      return name
    }

    println(f5("xxx"))

    println("==========================")

    //    (6)Scala如果期望是無返回值類型,可以省略等號
    def f6(name: String) {
      println(name)
    }

    println(f6("xxx"))

    println("==========================")

    //    (7)如果函數(shù)無參街夭,但是聲明了參數(shù)列表,那么調用時躏筏,小括號板丽,可加可不加
    def f7(): Unit = {
      println("xxx")
    }

    f7()
    f7

    println("==========================")

    //    (8)如果函數(shù)沒有參數(shù)列表,那么小括號可以省略趁尼,調用時小括號必須省略
    def f8: Unit = {
      println("xxx")
    }

//    f8()
    f8

    println("==========================")

    //    (9)如果不關心名稱埃碱,只關心邏輯處理,那么函數(shù)名(def)可以省略
    def f9(name: String): Unit = {
      println(name)
    }

    // 匿名函數(shù)酥泞,lambda表達式
    (name: String) => { println(name) }

    println("==========================")
    
    def f10 = (x:String)=>{println("wusong")}
    def f11(f:String=>Unit) = {
    f("")
    }
    f10(f0)
    println(f10((x:String)=>{println("wusong")}))


   // 匿名函數(shù)的簡化原則
   f((name: String) => {
      println(name)
    })
   
    //    (1)參數(shù)的類型可以省略砚殿,會根據(jù)形參進行自動的推導
    f((name) => {
      println(name)
    })

    //    (2)類型省略之后,發(fā)現(xiàn)只有一個參數(shù)芝囤,則圓括號可以省略似炎;其他情況:沒有參數(shù)和參數(shù)超過1的永遠不能省略圓括號。
    f( name => {
      println(name)
    })

    //    (3)匿名函數(shù)如果只有一行悯姊,則大括號也可以省略
    f( name => println(name) )

    //    (4)如果參數(shù)只出現(xiàn)一次羡藐,則參數(shù)省略且后面參數(shù)可以用_代替,參數(shù)名只在函數(shù)中出現(xiàn)一次
    f( println(_) )

    //     (5) 如果可以推斷出悯许,當前傳入的println是一個函數(shù)體仆嗦,而不是調用語句,可以直接省略下劃線
    f( println )

    println("=========================")
  }
}

3. 函數(shù)高級

3.1 高階函數(shù)

1)函數(shù)作為值傳遞

object HighOrderFunction {
  def main(args: Array[String]): Unit = {
    def f(n: Int): Int = {
      println("f調用")
      n + 1
    }
   // 1.普通的函數(shù)調用
    val result: Int = f(123)
    println(result)

    // 2. 函數(shù)作為值進行傳遞先壕,有點像重命名
    // f1,f2要的是函數(shù)體瘩扼,而不是觸發(fā)函數(shù)
    val f1: Int=>Int = f
    val f2 = f _
    //f1谆甜,f2輸出的是函數(shù)對象,對象引用不同
    println(f1)
    println(f1(12))
    println(f2)
    println(f2(35))
    // 3 無參函數(shù)
    def fun(): Int = {
      println("fun調用")
      1
    }
    //調用函數(shù)體
    val f3: ()=>Int = fun
    val f4 = fun _
    println(f3)
    println(f4)
   //注意:如果寫val f4 = fun 那這就是一次函數(shù)
    
  }
}

輸出

f調用
124

com.pl.HighOrderFunction$$$Lambda$5/1510067370@19bb089b
f調用
13
com.pl.HighOrderFunction$$$Lambda$6/1908923184@4563e9ab

com.pl.HighOrderFunction$$$Lambda$7/1289479439@7cf10a6f
com.pl.HighOrderFunction$$$Lambda$8/6738746@7e0babb1

2)函數(shù)作為參數(shù)傳遞

object Test1 {
  def main(args: Array[String]): Unit = {
 // (1)定義一個函數(shù)集绰,函數(shù)參數(shù)還是一個函數(shù)簽名店印;f 表示函數(shù)名稱;(Int,Int) 表示輸入兩個 Int 參數(shù);Int 表示函數(shù)返回值
    def dualEval(op:(Int,Int)=>Int,a:Int,b:Int):Int={
      op(a,b)
    }
 // (1)定義一個函數(shù)倒慧,函數(shù)參數(shù)還是一個函數(shù)簽名按摘;f 表示函數(shù)名稱;(Int,Int) 表示輸入兩個 Int 參數(shù);Int 表示函數(shù)返回值
    def add(a:Int,b:Int):Int={
      a+b;
    }
   // (3)將 add 函數(shù)作為參數(shù)傳遞給 f1 函數(shù)纫谅,如果能夠推斷出來不是調用
    println(dualEval(add,1,2))
    println(dualEval((a,b)=>a+b,1,2))

  }
}

3)函數(shù)作為函數(shù)返回值返回

package chapter05.test

object Test1 {
  def main(args: Array[String]): Unit = {
  
    // 3. 函數(shù)作為函數(shù)的返回值返回
    // Int=>Unit 返回函數(shù)的入?yún)⒑头祷刂?    def f5(): Int=>Unit = {
      def f6(a: Int): Unit = {
        println("f6調用 " + a)
      }
      f6    // 將函數(shù)直接返回
    }

        val f6 = f5()
        println(f6)
        println(f6(25))

    println(f5()(25))
  }
}

輸出

chapter05.test.Test1$$$Lambda$1/1989780873@47f37ef1    // 返回函數(shù)對象
f6調用 25
()
f6調用 25
()

4) 引用案例

? 其實高階函數(shù)的一個應用比較多的場合是:定義集合中數(shù)據(jù)的操作炫贤,將操作抽象出來。

object Test07_Practice_CollectionOperation {
  def main(args: Array[String]): Unit = {
    val arr: Array[Int] = Array(12, 45, 75, 98)

    // 對數(shù)組進行處理付秕,將操作抽象出來兰珍,處理完畢之后的結果返回一個新的數(shù)組
    def arrayOperation(array: Array[Int], op: Int=>Int): Array[Int] = {
      for (elem <- array) yield op(elem)
    }

    // 定義一個加一操作
    def addOne(elem: Int): Int = {
      elem + 1
    }

    // 調用函數(shù),傳遞函數(shù)  這里可以看出arrayOperation只是定義函數(shù)處理的大致流程,具體的邏輯推遲到調用方询吴,和map的邏輯很符合
    val newArray: Array[Int] = arrayOperation(arr, addOne)

    println(newArray.mkString(","))

    // 傳入匿名函數(shù)掠河,實現(xiàn)元素翻倍
    val newArray2 = arrayOperation(arr, _ * 2)
    println(newArray2.mkString(", "))
  }
}

5)擴展練習

(1) 定義一個匿名函數(shù),并將它作為值賦給變量 fun猛计。函數(shù)有三個參數(shù)唠摹,類型分別為 Int,String奉瘤,Char勾拉,返回值類型為 Boolean。 要求調用函數(shù) fun(0, “”, ‘0’)得到返回值為 false盗温,其它情況均返回 true藕赞。

object Test08_Practice {
  def main(args: Array[String]): Unit = {
    // 1. 練習1
    val fun = (i: Int, s: String, c: Char) => {
      if (i == 0 && s == "" && c == '0') false else true
    }

    println(fun(0, "", '0'))
    println(fun(0, "", '1'))
    println(fun(23, "", '0'))
    println(fun(0, "hello", '0'))

    println("===========================")

   
  }
}

(2) 定義一個函數(shù) func,它接收一個 Int 類型的參數(shù)卖局,返回一個函數(shù)(記作 f1)斧蜕。 它返回的函數(shù) f1,接收一個 String 類型的參數(shù)砚偶,同樣返回一個函數(shù)(記作 f2)批销。函數(shù) f2 接 收一個 Char 類型的參數(shù),返回一個 Boolean 的值蟹演。 要求調用函數(shù) func(0) (“”) (‘0’)得到返回值為 false风钻,其它情況均返回 true。

object Test08_Practice {
  def main(args: Array[String]): Unit = {
    // 2. 練習2
    def func(i: Int): String=>(Char=>Boolean) = {
      def f1(s: String): Char=>Boolean = {
        def f2(c: Char): Boolean = {
          if (i == 0 && s == "" && c == '0') false else true
        }
        f2
      }
      f1
    }

    println(func(0)("")('0'))
    println(func(0)("")('1'))
    println(func(23)("")('0'))
    println(func(0)("hello")('0'))

    // 匿名函數(shù)簡寫
    def funcc(i: Int): String=>(Char=>Boolean) = {
      //匿名函數(shù)首先不需要知道名字酒请,且返回值不用寫即所有參數(shù)類型的定義省略
     // def f1(s: String): Char=>Boolean = {
      (s: String) =>{
       (c: Char)=> {
          if (i == 0 && s == "" && c == '0') false else true
        }
      }
    }
    //當然還可以進一步縮寫
    //如果在外側已經(jīng)將形參類型定義好骡技,那么內層的匿名函數(shù)形參也都是可以確定的
    def func1(i: Int): String=>(Char=>Boolean) = {
      s => c => if (i == 0 && s == "" && c == '0') false else true
    }

    println(func1(0)("")('0'))
    println(func1(0)("")('1'))
    println(func1(23)("")('0'))
    println(func1(0)("hello")('0'))

    // 上面的簡寫還可以進一步省略,將 String=>(Char=>Boolean)省略
    // 函數(shù)的柯里化
    def func2(i: Int)(s: String)(c: Char): Boolean = {
      if (i == 0 && s == "" && c == '0') false else true
    }
    println(func2(0)("")('0'))
    println(func2(0)("")('1'))
    println(func2(23)("")('0'))
    println(func2(0)("hello")('0'))
  }
}

3.2 閉包&柯里化

閉包

  • 如果一個函數(shù),訪問到了它的外部(局部)變量的值布朦,那么這個函數(shù)和他所處的 環(huán)境囤萤,稱為閉包。
  • 即內部函數(shù)將依賴的外部變量保存在本函數(shù)中是趴,延長了外部函數(shù)局部變量的生命周期涛舍。
  • scala中調用函數(shù)相當于創(chuàng)建了一個對象實例,對象實例在heap中唆途,改對象實例打包保存了該對象的環(huán)境(外部環(huán)境和局部變量)富雅,所以不會因為方法彈棧而丟失方法局部變量。
    • 比如上面例子中的func2(0)("")('0')肛搬,實際上的調用順序是:func2>f1>f2没佑,f2并不會因為前兩者的彈棧而丟失其依賴的局部變量。

函數(shù)柯里化

  • 把一個參數(shù)列表的多個參數(shù)變成多個參數(shù)列表温赔。
  • 一般純函數(shù)式編程就是定義自變量和因變量之間的關系蛤奢,只有一個入?yún)ⅲ玫揭粋€因變量陶贼,不存在輸入多個自變量得出一個因變量的用法啤贩。
  • 但是scala中因為需要兼容java和函數(shù)式編程,所以沒有只有一個入?yún)⒌南拗瓢菅恚梢栽试S多個入?yún)ⅰ?/li>
  • 函數(shù)柯里化可以實現(xiàn)這么一個效果痹屹,每一層調用只有一個入?yún)ⅲ砸粋€參數(shù)列表多個參數(shù)實際可以變成多個參數(shù)列表腹纳。
object Test09_ClosureAndCurrying {
  def main(args: Array[String]): Unit = {
    def add(a: Int, b: Int): Int = {
      a + b
    }

    // 1. 考慮固定一個加數(shù)的場景
    def addByFour(b: Int): Int = {
      4 + b
    }

    // 2. 擴展固定加數(shù)改變的情況
    def addByFive(b: Int): Int = {
      5 + b
    }

    // 3. 將固定加數(shù)作為另一個參數(shù)傳入痢掠,但是是作為”第一層參數(shù)“傳入
    def addByFour1(): Int=>Int = {
      val a = 4
      def addB(b: Int): Int = {
        a + b
      }
      addB
    }

    def addByA(a: Int): Int=>Int = {
      def addB(b: Int): Int = {
        a + b
      }
      addB
    }

    println(addByA(35)(24))
    println(addByA(35))

    val addByFour2 = addByA(4)
    val addByFive2 = addByA(5)

    println(addByFour2(13))
    println(addByFive2(25))

    // 4. lambda表達式簡寫
    def addByA1(a: Int): Int=>Int = {
      //def 函數(shù)名 返回值均省略
      (b: Int) => {
        a + b
      }
    }
    //進一步簡寫  省略形參
    def addByA2(a: Int): Int=>Int = {
      //def 函數(shù)名 形參 返回值均省略
      b => a + b
    }
    // 進一步簡寫  參數(shù)只出現(xiàn)一次,函數(shù)只有一行
    def addByA3(a: Int): Int=>Int = a + _
    val addByFour3 = addByA3(4)
    val addByFive3 = addByA3(5)

    println(addByFour3(13))
    println(addByFive3(25))

    // 5. 柯里化  該函數(shù)分為兩層嘲恍,幾個參數(shù)列表幾層  一旦用到柯里化,那么其底層必然是閉包
    def addCurrying(a: Int)(b: Int): Int = {
      a + b
    }

    println(addCurrying(35)(24))
  }
}

輸出

59
chapter05.Test09_ClosureAndCurrying$$$Lambda$5/1510067370@2ff4f00f   //println(addByA(35))實際輸出的是函數(shù)對象實例
17
30
17
30
59

3.3 遞歸

object Test10_Recursion {
  def main(args: Array[String]): Unit = {
    println(fact(5))
    println(tailFact(5))
  }

  // 階乘
 // 遞歸算法
 // 1) 方法調用自身
 // 2) 方法必須要有跳出的邏輯
 // 3) 方法調用自身時雄驹,傳遞的參數(shù)應該有規(guī)律
 // 4) scala 中的遞歸必須聲明函數(shù)返回值類型
  // 遞歸實現(xiàn)計算階乘
  def fact(n: Int): Int = {
    if (n == 0) return 1
    //最后一層代碼可以省略 return
    fact(n - 1) * n
  }
  //上面這種遞歸方式有一個很大的確定就是:每層遞歸都需要產(chǎn)生新的棧幀佃牛,如果層數(shù)很多,會導致當前的棧中需要保存的棧幀非常多医舆,甚至會出現(xiàn)stack over flow
  //遞歸是以耗費椃溃空間資源為代價的
  //函數(shù)式編程語言中提供了一種優(yōu)化方式:每層棧幀覆蓋之前的棧幀,只消耗一個棧幀
  
  // 尾遞歸實現(xiàn)
  def tailFact(n: Int): Int = {
    //尾遞歸 保存每次該層的結果currRes蔬将,將每層的結果值不停的往下傳爷速,這樣每次調用就不需要保存上一層的調用信息了
    // @tailrec 可以確保寫出的是一個尾遞歸,如果不是會報錯
    @tailrec
    def loop(n: Int, currRes: Int): Int = {
      if (n == 0) return currRes
      loop(n - 1, currRes * n)
    }
    //從1開始算階乘
    loop(n, 1)
  }
}

3.4 控制抽象

控制抽象

  • 強調的是參數(shù)的調用方式
    • 傳值調用:將一個確定的值當做參數(shù)傳遞霞怀。
    • 傳名調用:將代碼塊當做參數(shù)傳遞惫东。
object Test11_ControlAbstraction {
  def main(args: Array[String]): Unit = {
    // 1. 傳值參數(shù)
    def f0(a: Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }
    f0(23)

    def f1(): Int = {
      println("f1調用")
      12
    }
    f0(f1())

    println("========================")

    // 2. 傳名參數(shù),傳遞的不再是具體的值,而是代碼塊
    // a: =>Int 該入?yún)⒖梢允谴a塊廉沮,返回值是int 
    def f2(a: =>Int): Unit = {
      //如果a是代碼塊颓遏,那么a每出現(xiàn)一次就執(zhí)行一遍對應的代碼塊
      println("a: " + a)
      println("a: " + a)
    }

    f2(23)
    f2(f1())

    f2({
      println("這是一個代碼塊")
      29
    })

  }
}

控制抽象傳名參數(shù)的特性豐富了scala的功能,比如利用該特性實現(xiàn)自定義關鍵字滞时。

object Test12_MyWhile {
  def main(args: Array[String]): Unit = {
    var n = 10

    // 1. 常規(guī)的while循環(huán)
    while (n >= 1){
      println(n)
      n -= 1
    }

    // 2. 用閉包實現(xiàn)一個函數(shù)叁幢,將代碼塊作為參數(shù)傳入,遞歸調用  入?yún)?返回值 都是代碼塊
    def myWhile(condition: =>Boolean): (=>Unit)=>Unit = {
      // 內層函數(shù)需要遞歸調用坪稽,參數(shù)就是循環(huán)體
      def doLoop(op: =>Unit): Unit = {
        if (condition){
          op
          myWhile(condition)(op)
        }
      }
      doLoop _
    }

    println("=================")
    n = 10
    myWhile(n >= 1){
      println(n)
      n -= 1
    }

    // 3. 用匿名函數(shù)實現(xiàn)
    def myWhile2(condition: =>Boolean): (=>Unit)=>Unit = {
      // 內層函數(shù)需要遞歸調用曼玩,參數(shù)就是循環(huán)體
      op => {
        if (condition){
          op
          myWhile2(condition)(op)
        }
      }
    }
    println("=================")
    n = 10
    myWhile2(n >= 1){
      println(n)
      n -= 1
    }

    // 3. 用柯里化實現(xiàn)
    def myWhile3(condition: =>Boolean)(op: =>Unit): Unit = {
      if (condition){
        op
        myWhile3(condition)(op)
      }
    }

    println("=================")
    n = 10
    myWhile3(n >= 1){
      println(n)
      n -= 1
    }
  }
}

3.5惰性加載

惰性加載

  • 當函數(shù)返回值被聲明為 lazy 時,函數(shù)的執(zhí)行將被推遲窒百,直到我們首次對此取值黍判,該函 數(shù)才會執(zhí)行。
  • 即推遲函數(shù)的執(zhí)行時期贝咙,只有第一次需要調用的時候才觸發(fā)其邏輯样悟。
object Test13_Lazy {
  def main(args: Array[String]): Unit = {
    //惰性加載和控制抽象差不多,只是控制抽象將代碼塊給入?yún)⑼バ桑栊约虞d將代碼塊給val
    lazy val result: Int = sum(13, 47)

    println("1. 函數(shù)調用")
    println("2. result = " + result)
    println("4. result = " + result)
  }

  def sum(a: Int, b: Int): Int = {
    println("3. sum調用")
    a + b
  }
}

輸出

1. 函數(shù)調用
3. sum調用  //sum函數(shù)第一次調用
2. result = 60
4. result = 60   //sum函數(shù)只調用一次將值傳給了常量result窟她,后續(xù)不會再調用該函數(shù)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蔼水,隨后出現(xiàn)的幾起案子震糖,更是在濱河造成了極大的恐慌,老刑警劉巖趴腋,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吊说,死亡現(xiàn)場離奇詭異,居然都是意外死亡优炬,警方通過查閱死者的電腦和手機颁井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠢护,“玉大人雅宾,你說我怎么就攤上這事】叮” “怎么了眉抬?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懈凹。 經(jīng)常有香客問我蜀变,道長,這世上最難降的妖魔是什么介评? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任库北,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘贤惯。我一直安慰自己洼专,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布孵构。 她就那樣靜靜地躺著屁商,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颈墅。 梳的紋絲不亂的頭發(fā)上蜡镶,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音恤筛,去河邊找鬼官还。 笑死,一個胖子當著我的面吹牛毒坛,可吹牛的內容都是我干的望伦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼煎殷,長吁一口氣:“原來是場噩夢啊……” “哼屯伞!你這毒婦竟也來了?” 一聲冷哼從身側響起豪直,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤劣摇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弓乙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體末融,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年暇韧,在試婚紗的時候發(fā)現(xiàn)自己被綠了勾习。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡懈玻,死狀恐怖语卤,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情酪刀,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布钮孵,位于F島的核電站骂倘,受9級特大地震影響,放射性物質發(fā)生泄漏巴席。R本人自食惡果不足惜历涝,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荧库,春花似錦堰塌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚪战,卻和暖如春牵现,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邀桑。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工瞎疼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壁畸。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓贼急,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捏萍。 傳聞我的和親對象是個殘疾皇子太抓,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容