深入理解Scala函數式編程過程

這篇文章主要介紹了深入理解Scala函數式編程過程的相關資料,希望通過本文能幫助到大家,讓大家學習理解這部分內容,需要的朋友可以參考下

深入理解Scala函數式編程過程

我們馬上開始一段變態的過程

如果要求立方和,可以這麼做

35 * 35 * 35 
68 * 68 * 68 

沒毛病,抽象一點兒,寫個函數:

def cube(n: Int) = n * n * n 
cube(35) 
cube(68)

省事兒了,如果求1到10的立方和,OK,寫個遞歸

def cube(n: Int) = n * n * n 
 def sumCube(a: Int, b: Int): Int = 
   if (a > b) 0 else cube(a) + sumCube(a + 1, b) 
 
sumCube(1, 10)

變態一點兒,立方和,平方和,階乘和,依舊寫出它們的函數並且依次計算沒毛病

def cube(n: Int) = n * n * n 
def id(n: Int) = n 
def square(n : Int) = n * n 
def fact(n: Int): Int = 
  if (n == 0) 1 else n * fact(n - 1) 
 
def sumCube(a: Int, b: Int): Int = 
  if (a > b) 0 else cube(a) + sumCube(a + 1, b) 
 
def sumSquare(a: Int, b: Int): Int = 
  if(a > b) 0 else square(a) + sumSquare(a + 1, b) 
  
def sumFact(a: Int, b: Int): Int = 
  if (a > b) 0 else fact(a) + sumFact(a + 1, b) 
 
def sumInt(a: Int, b: Int): Int = 
  if(a > b) 0 else id(a) + sumInt(a + 1, b)  
 
 sumCube(1, 10) 
 sumInt(1, 10) 
 sumSquare(1, 10) 
 sumFact(1, 10)

然後你發現,你已經寫了一堆同樣邏輯的if else,看起來不奇怪麼,這種無腦的操作當然要避免:

我們要把這些函數名不同但是處理邏輯相同的渣渣都封裝到一個函數中,並且這個函數將作爲參數賦值到高階函數中,運行的結果只跟傳入的參數類型有關係,也就是把cube,square,fact,泛化成一個f

def cube(n: Int) = n * n * n 
def id(n: Int) = n 
def square(n : Int) = n * n 
def fact(n: Int): Int = 
  if (n == 0) 1 else n * fact(n - 1) 
//高階函數
def sum(f: Int=>Int, a:Int, b:Int): Int = 
  if(a>b) 0 else f(a)+sum(f, a+1, b)
// 使用高階函數重新定義求和函數
def sumCube(a: Int, b: Int): Int = sum(cube, a, b) 
def sumSquare(a: Int, b: Int): Int = sum(square, a, b) 
def sumFact(a: Int, b: Int): Int = sum(fact, a, b) 
def sumInt(a: Int, b: Int): Int = sum(id, a, b) 
 
 sumCube(1, 10) 
 sumInt(1, 10) 
 sumSquare(1, 10) 
 sumFact(1, 10)

但是這樣寫,還有個問題,就是前面定義了一堆cube,id的初始定義,後面還要繼續定義,實際上就是套了一層包裝,不要了,去掉,使用匿名函數的功能來將調用進一步簡化。多數情況下,我們關心的是高階函數,而不是作爲參數傳入的函數,所以爲其單獨定義一個函數是沒有必要的。值得稱讚的是 Scala 中定義匿名函數的語法很簡單,箭頭左邊是參數列表,右邊是函數體,參數的類型是可省略的,Scala 的類型推測系統會推測出參數的類型。使用匿名函數後,我們的代碼變得更簡潔了:

//保留邏輯較爲複雜的函數
def fact(n: Int): Int = 
if (n == 0) 1 else n * fact(n - 1) 
 
def sum(f: Int => Int, a: Int, b: Int): Int = 
  if (a > b) 0 else f(a) + sum(f, a + 1, b) 
 
// 使用高階函數重新定義求和函數
def sumCube(a: Int, b: Int): Int = sum(x => x * x * x, a, b) 
def sumSquare(a: Int, b: Int): Int = sum(x => x * x, a, b) 
def sumFact(a: Int, b: Int): Int = sum(fact, a, b) 
def sumInt(a: Int, b: Int): Int = sum(x => x, a, b) 
 

sumCube(1, 10) 
sumInt(1, 10) 
sumSquare(1, 10) 
sumFact(1, 10)

寫到這裏問題解決的差不多了,但是我們仔細想想,函數式編程的真諦,一個輸入到另一個輸出,而不是像這樣兩個參數傳來傳去,看起來很麻煩,於是乎

def fact(n: Int): Int = 
if (n == 0) 1 else n * fact(n - 1) 
 
// 高階函數
def sum(f: Int => Int): (Int, Int) => Int = { 
  def sumF(a: Int, b: Int): Int = 
   if (a > b) 0 else f(a) + sumF(a + 1, b) 
 
  sumF 
} 
// 使用高階函數重新定義求和函數
def sumCube: Int = sum(x => x * x * x) 
def sumSquare: Int = sum(x => x * x) 
def sumFact: Int = sum(fact) 
def sumInt: Int = sum(x => x) 
 
// 這些函數使用起來還和原來一樣 ! 
sumCube(1, 10) 
sumInt(1, 10) 
sumSquare(1, 10) 
sumFact(1, 10)

實際上這個時候sum裏面傳入的已經是匿名函數了,類似於g(f(x))裏面的f(x), 你還需要去調用那個f(x)而不是去腦補運算.

我們再來開一下腦洞,既然sum返回的是一個函數,我們可以直接使用這些函數,沒有必要再重複寫一遍調用命令了,sumCube(1, 10) 類的語句可以省去不要了。

def fact(n: Int): Int = 
  if (n == 0) 1 else n * fact(n - 1) 
 
// 高階函數
def sum(f: Int => Int): (Int, Int) => Int = { 
  def sumF(a: Int, b: Int): Int = 
   if (a > b) 0 else f(a) + sumF(a + 1, b) 
  sumF 
}

// 直接調用高階函數 ! 
sum(x => x * x * x) (1, 10) //=> sumCube(1, 10) 
sum(x => x) (1, 10)      //=> sumInt(1, 10) 
sum(x => x * x) (1, 10)   //=> sumSquare(1, 10) 
sum(fact) (1, 10)       //=> sumFact(1, 10)

最後我們還可以使用高階函數的語法糖來進一步優化這段代碼: 

// 沒使用語法糖的 sum 函數
 def sum(f: Int => Int): (Int, Int): Int = { 
 def sumF(a: Int, b: Int): Int = 
  if (a > b) 0 else f(a) + sumF(a + 1, b) 
 
 sumF 
} 
// 使用語法糖後的 sum 函數
 def sum(f: Int => Int)(a: Int, b: Int): Int = 
 if (a > b) 0 else f(a) + sum(f)(a + 1, b)

我反而覺得用語法糖更容易理解一點,更傾向於我們學的數學語言。

讀者可能會問:我們把原來的sum函數轉化成這樣的形式,好處在哪裏?答案是我們獲得了更多的可能性,比如剛開始求和的上下限還沒確定,我們可以在程序中把一個函數傳給sum, sum(fact)完全是一個合法的表達式,待後續上下限確定下來時,再把另外兩個參數傳進來。對於 sum 函數,我們還可以更進一步,把 a,b 參數再轉化一下,這樣 sum 函數就變成了這樣一個函數:它每次只能接收一個參數,然後返回另一個接收一個參數的函數,調用後,又返回一個只接收一個參數的函數。這就是傳說中的柯里化,多麼完美的形式!在現實世界中,的確有這樣一門函數式編程語言,那就是 Haskell,在 Haskell 中,所有的函數都是柯里化的,即所有的函數只接收一個參數!

// 柯里化後的 sum 函數
 def sum(f: Int => Int)(a: Int) (b: Int): Int = 
if (a > b) 0 else f(a) + sum(f)(a + 1)(b) 
 
// 使用柯里化後的高階函數 ! 
 sum(x => x * x * x)(1)(10) //=> sumCube(1, 10) 
 sum(x => x)(1)(10)      //=> sumInt(1, 10)
 

如有疑問請留言或者到本站社區交流討論,感謝閱讀希望能幫助到大家,謝謝大家對本站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章