函數式編程(Functional Programming)是一種編程風格,它是相對於指令式編程風格而言的,常見的面向對象編程就是指令式編程風格。 指令式編程是面向電腦硬體的抽象,有變數(對應著存儲單元),賦值語句(獲取、存儲指令),表達式(記憶體引用和算術運算)和控制語句(跳轉語句)。 而函數式編程 ...
函數式編程(Functional Programming)是一種編程風格,它是相對於指令式編程風格而言的,常見的面向對象編程就是指令式編程風格。
指令式編程是面向電腦硬體的抽象,有變數(對應著存儲單元),賦值語句(獲取、存儲指令),表達式(記憶體引用和算術運算)和控制語句(跳轉語句)。
而函數式編程是面向數學的抽象,將計算描述為一種表達式求值。這裡的函數實際就是數學中的函數,即自變數到因變數的映射。也就是說,一個函數的值僅決定於函數參數的值,不依賴其他狀態。
函數式編程是一種抽象程度很高的編程範式,純粹的函數式編程語言編寫的函數沒有變數,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。而允許使用變數的程式設計語言,由於函數內部的變數狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。
在函數式語言當中,函數作為一等公民,可以在任何地方定義,在函數內或函數外,可以作為函數的參數或返回值,可以對函數進行組合,也可以將函數賦值給變數。嚴格意義上的函數式編程意味著不適用可變的變數,賦值,迴圈和其他命令式控制結構進行編程。
函數式編程風格帶來的好處是:
- 函數式編程使用不可變對象作為變數,不會修改變數的值,而是返回一個新的值,如此這樣,更容易理清頭緒,使得單元測試和調試更加容易;
- 可以很自由地傳遞不可變對象,但對於可變對象來說,傳遞給其他代碼之前,需要先建造一個以防萬一的副本;
- 一旦不可變對象完成構造以後,就不會有線程因為併發訪問而破壞對象內部狀態,因為根本沒有線程可以改變不可變對象的狀態;
- 不可變對象讓哈希表鍵值更安全,所以哈希表鍵要求必須是不可變對象,否則使用可變對象,如果對象狀態發生變化,那麼在哈希表中就找不到這個對象了;
具體到編程語言,Scala(靜態語言)和Python(動態語言)都能比較的支持函數式編程風格,但是它們都不是純函數式的,也就是說它們同時支持指令式風格和函數式風格。而Java基本是指令式風格,但自從Java8引入lambda表達式以後也開始部分支持函數式風格。函數式編程最典型的是諸如map, flatMap, reduce, filter等函數,它們的特點是支持某個函數作為上面這些函數的參數。
下麵分別以Java、Scala和Python舉例函數式編程,其中Java對函數式編程只是間接的支持(通過函數式介面),支持度比較有限,而Scala和Python就對函數式編程支持的比較好。
Java函數式編程舉例:
1 package lxy.java.fp; 2 3 import java.util.*; 4 import java.util.function.*; 5 6 7 public class FPDemo { 8 //定義泛型方法,用以根據第二個參數指定的條件從第一個參數指定的集合中過濾部分元素,並返回過濾後的結果。這裡的第二個參數是一個函數式介面。 9 public static <T> List <T> filter(List <T> list, Predicate <T> p) { 10 List <T> results = new ArrayList <>(); 11 for (T s : list) { 12 if (p.test(s)) { 13 results.add(s); 14 } 15 } 16 return results; 17 } 18 19 public static void main(String[] args) { 20 List <String> myList = Arrays.asList("Hello", "Java", "Python", "Scala"); 21 22 //通過匿名類的方式 23 List <String> results = filter(myList, new Predicate <String>() { 24 public boolean test(String t) { 25 return t.length() >= 5; 26 } 27 }); 28 System.out.println("through anonymous class:"); 29 System.out.println("strings with length more than 5:"); 30 for (String result : results) { 31 System.out.println(result); 32 } 33 34 //行為參數化,通過匿名函數(即lambda表達式)方式, 35 System.out.println("through lambda expression:"); 36 System.out.println("strings with length more than 5:"); 37 List <String> results2 = filter(myList, s -> s.length() >= 5); 38 results2.forEach(s -> System.out.println(s)); 39 40 //很容易地將過濾條件由字元串長度大於等於5改為字元串以字母a結尾, 41 // 這就是行為參數化,即將具體的邏輯(即行為或者函數)參數化,使得filter函數更加抽象,提高了代碼復用度 42 //否則需要寫2個filter函數,一個過濾出長度大於等於5的字元串,另一個過濾出以字元a結尾的字元串 43 System.out.println("strings ends with character 'a'"); 44 List <String> results3 = filter(myList, s -> s.endsWith("a")); 45 results3.forEach(System.out::println); 46 47 //Java流中的filter函數和map函數,註意這裡的filter是Java庫函數,和前面自定義的filter函數不一樣 48 System.out.println("strings with length more than 5 and its length:"); 49 myList.parallelStream().filter(s -> s.length() >= 5).map(s -> "(" + s + ", " + s.length() + ")") 50 .forEach(System.out::println); 51 52 //高階函數 53 Function <Integer, Integer> f = (Integer x) -> x + 1; 54 Function <Integer, Integer> g = (Integer x) -> x * x; 55 Function <Integer, Integer> h = f.andThen(g); 56 Function <Integer, Integer> r = f.compose(g); 57 58 System.out.println("higher-order function:"); 59 System.out.println("h(2)=g(f(2))=" + h.apply(2)); 60 System.out.println("r(2)=f(g(2))=" + r.apply(2)); 61 62 } 63 }
Scala函數式編程舉例:
1 package lxy.scala.fp 2 3 object FPDemo { 4 //定義泛型方法,用以根據第二個參數指定的條件從第一個參數指定的列表中過濾部分元素,並返回過濾後的結果,結果類型仍然是List。 5 //這裡的第二個參數是一個函數, 該函數輸入參數類型為T,返回值類型為Boolean 6 def filter[T](list: List[T], f: T => Boolean) = 7 for {e <- list if f(e) == true} yield e 8 9 //高階函數,該函數定義為g(f(x)),其中函數f和g都是作為參數在調用該高階函數時指定的 10 def highOrderFunction1(x: Int, f: Int => Int, g: Int => Int) = g(f(x)) 11 12 //定義嵌套函數,針對每個參數,外層函數都會返回一個函數,即內層函數 13 //這裡factor是自由變數,number是綁定變數。 14 //閉包是一個函數,返回值依賴於聲明在函數外部的一個或多個變數 15 //函數multiplier返回的函數就是閉包,factor就是外部的變數,也叫自由變數,number是綁定變數(形式參數) 16 def multiplier(factor: Int): Int => Int = { 17 def multiplyByFactor(number: Int) = factor * number 18 19 return multiplyByFactor 20 } 21 22 //柯里化函數,類型是(Int)(Int) => Int 23 def multiplier2(factor: Int)(number: Int) = factor * number 24 25 //這個函數實際跟multiplier效果是一樣的,每傳入一個參數factor,都會返回一個函數 26 //閉包是一個函數,返回值依賴於聲明在函數外部的一個或多個變數 27 //函數multiplier3返回的函數就是閉包,factor就是外部的變數,也叫自由變數,number是綁定變數(形式參數) 28 def multiplier3(factor: Int) = multiplier2(factor) _ 29 30 def main(args: Array[String]): Unit = { 31 val myList = List("Hello", "Java", "Python", "Scala") 32 println("strings with length more than 5") 33 filter(myList, (s: String) => s.length >= 5).foreach(s => println(s)) 34 35 println("strings ends with character 'a'") 36 filter(myList, (s: String) => s.endsWith("a")).foreach(println) 37 38 println("strings with length more than 5 and its length:") 39 //這裡的filter不是自定義函數filter,而是庫函數,返回長度大於等於5的字元串以及對應的長度 40 myList.filter(_.length >= 5).map(s => (s, s.length)).foreach(println) 41 42 43 val result = highOrderFunction1(2, (x: Int) => x + 1, (x: Int) => x * x) 44 println("f(x)=x+1, g(x)=x*x, g(f(2))=" + result) 45 46 47 println("multiplier(2)=" + multiplier(2)) 48 println("multiplier(2)(3)=" + multiplier(2)(3)) 49 val double = multiplier(2) 50 println("double(3)=" + double(3)) 51 52 println("multiplier3(3)=" + multiplier3(3)) 53 println("multiplier3(3)(4)=" + multiplier3(3)(4)) 54 val triple = multiplier3(3) 55 println("triple(4)=" + triple(4)) 56 57 } 58 }
Python函數式編程舉例:
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 4 if __name__ == "__main__": 5 """ 6 Usage: ./fp_demo.py 7 """ 8 9 # 定義函數filter,第一個參數是列表,第二個參數是函數 10 def filter(list, f): 11 return [e for e in list if f(e) == True] 12 13 # python中沒有foreach函數,自己定義一個,其中第一個參數是函數,第二個參數是迭代器(列表) 14 def foreach(f, iterator): 15 for item in iterator: 16 f(item) 17 18 myList = ["Hello", "Java", "Python", "Scala"] 19 resultList = filter(myList, lambda e: len(e) >= 5) 20 print("strings with length more than 5", sep="\n") 21 foreach(lambda e: print(e, sep="\n"), resultList) 22 23 resultList2 = filter(myList, lambda e: e.endswith("a")) 24 print("strings ends with character 'a'", sep="\n") 25 foreach(lambda e: print(e, sep="\n"), resultList2) 26 27 # 這裡的map函數是python內置的 28 print("strings with length more than 5 and its length:", sep="\n") 29 foreach(lambda e: print(e, sep="\n"), map(lambda e: (e, len(e)), filter(myList, lambda e: len(e) >= 5))) 30 31 # 高階函數,該函數定義為g(f(x)),其中函數f和g都是作為參數在調用該高階函數時指定的 32 def highOrderFunction1(x, f, g): 33 return g(f(x)) 34 35 # 該函數在下麵對highOrderFunction1函數的調用中被當做參數傳入 36 def f(x): 37 return x + 1 38 39 # 第二個參數傳入上面定義的f函數,作為第三個參數的的函數採用的是匿名函數 40 result = highOrderFunction1(2, f, g=lambda x: x * x) 41 print("f(x)=x+1, g(x)=x*x, g(f(2))=%d" % result, sep="\n") 42 43 # 定義嵌套函數,針對每個參數,外層函數都會返回一個函數,即內層函數 44 # 這裡factor是自由變數,number是綁定變數。 45 # 閉包是一個函數,返回值依賴於聲明在函數外部的一個或多個變數 46 # 函數multiplier返回的函數就是閉包,factor就是外部的變數,也叫自由變數,number是綁定變數(形式參數) 47 def multiplier(factor): 48 def multiplyByFactor(number): 49 return factor * number 50 return multiplyByFactor 51 52 53 print("multiplier(2)(3)=%s" % multiplier(2)(3)) 54 # double是一個函數,它將輸入參數乘以2倍以後返回 55 double = multiplier(2) 56 print("double(3)=%s" % double(3)) 57 58 print("multiplier(3)(4)=%s" % multiplier(3)(4)) 59 # triple是一個函數,它將輸入參數乘以3倍以後返回 60 triple = multiplier(3) 61 print("triple(4)=%s" % triple(4)) 62 63 # 第三個參數f是函數 64 def add(x, y, f): 65 return f(x) + f(y) 66 67 print("2 ^ 2 + 3 ^2 = %s" % add(2, 3, lambda x: x * x)) 68 print("(2 + 1) + (3 + 1) = %s" % add(2, 3, f))
最後來看一個數學題目,已知a<=b, 且a和b都是整數,求下麵三個公式的值。
可以看到這三個公式分別是求a~b的和,a~b的平方和,a~b各自的階乘的和。可以看到三個公式是類似的,都是求和,只不過分別是對自身求和,對自身的平方求和以及對自身的階乘求和,也就是說這裡有3個計算邏輯,需要對這3個計算邏輯計算出來的數求和。如果是指令式編程風格,就只能寫三個函數來解決問題。但是如果採用函數式編程風格,就可以只寫一個通用的求和函數來解決該問題,因為可以將這3個計算邏輯(函數)作為參數傳給之前的通用求和函數。下麵分別用Java,Scala和Python來解決該問題。
Java代碼
1 package lxy.java.fp; 2 3 import java.util.function.*; 4 5 6 public class Sum4Integers { 7 //通用求和函數,其中f是一個函數式介面,它接收一個整型參數並返回一個整型數值 8 //採用遞歸計算方法 9 private static int sum(Function<Integer, Integer> f, int a, int b) { 10 if (a > b) 11 return 0; 12 else 13 return f.apply(a) + sum(f, a + 1, b); 14 } 15 16 //求階乘函數,採用遞歸演算法,較複雜,不能作為匿名函數傳入上面的通用求和參數,因此需要預先定義 17 private static int factor(int x) { 18 if (x == 0) 19 return 1; 20 else 21 return x * factor(x - 1); 22 } 23 24 //求公式一函數,即求a~b的和 25 //作為參數的函數就是返回變數自身,較簡單,採用匿名函數 26 static int sumInts(int a, int b) { 27 return sum(x -> x, a, b); 28 } 29 30 //求公式二函數,即求a^2 ~ b^2的和 31 //作為參數的函數就是返回變數的平方,較簡單,採用匿名函數 32 static int sumSquares(int a, int b) { 33 return sum(x -> x * x, a, b); 34 } 35 36 //求公式三函數,即求a! ~ b!的和 37 //作為參數的函數就是求變數的階乘,較複雜(本身是遞歸函數),採用定義好的函數factor 38 static int sumFactors(int a, int b) { 39 return sum(Sum4Integers::factor, a, b); 40 } 41 42 public static void main(String[] args) { 43 System.out.println("1+2+3+4+5=" + sumInts(1, 5)); 44 System.out.println("1^2+2^2+3^2+4^2+5^2=" + sumSquares(1, 5)); 45 System.out.println("1!+2!+3!+4!+5!=" + sumFactors(1, 5)); 46 } 47 48 }
Scala代碼
1 package lxy.scala.fp 2 3 4 object Sum4Integers { 5 //通用求和函數 6 //採用遞歸計算方法 7 private def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b) 8 9 //求階乘函數,採用遞歸演算法,較複雜,不能作為匿名函數傳入上面的通用求和參數,因此需要預先定義 10 private def factor(x: Int): Int = if (x == 0) 1 else x * factor(x - 1) 11 12 //求公式一函數,即求a~b的和 13 //作為參數的函數就是返回變數自身,較簡單,採用匿名函數 14 def sumInts(a: Int, b: Int) = sum(x => x, a, b) 15 16 //求公式二函數,即求a^2 ~ b^2的和 17 //作為參數的函數就是返回變數的平方,較簡單,採用匿名函數 18 def sumSquares(a: Int, b: Int) = sum(x => x * x, a, b) 19 20 //求公式三函數,即求a! ~ b!的和 21 //作為參數的函數就是求變數的階乘,較複雜(本身是遞歸函數),採用定義好的函數factor 22 def sumFactors(a: Int, b: Int) = sum(factor, a, b) 23 24 def main(args: Array[String]): Unit = { 25 println("1+2+3+4+5=" + sumInts(1, 5)) 26 println("1^2+2^2+3^2+4^2+5^2=" + sumSquares(1, 5)) 27 println("1!+2!+3!+4!+5!=" + sumFactors(1, 5)) 28 } 29 30 }
Python代碼
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 4 if __name__ == "__main__": 5 """ 6 Usage: ./sum_integers.py 7 """ 8 9 # 通用求和函數,採用遞歸計算方法 10 def sum(f, a, b): 11 if a > b: 12 return 0 13 else: 14 return f(a) + sum(f, a + 1, b) 15 16 # 求階乘函數,採用遞歸演算法,較複雜,不能作為匿名函數傳入上面的通用求和參數,因此需要預先定義 17 def factor(x): 18 if x == 0: 19 return 1 20 else: 21 return x * factor(x - 1) 22 23 # 求公式一函數,即求a~b的和 24 # 作為sum函數的第一個參數的函數就是返回變數自身,較簡單,採用匿名函數 25 def sumInts(a, b): 26 return sum(lambda x: x, a, b) 27 28 # 求公式二函數,即求a^2 ~ b^2的和 29 # 作為sum函數的第一個參數的函數就是返回變數的平方,較簡單,採用匿名函數 30 def sumSquares(a, b): 31 return sum(lambda x: x * x, a, b) 32 33 # 求公式三函數,即求a! ~ b!的和 34 # 作為sum函數的第一個參數的函數就是求變數的階乘,較複雜(本身是遞歸函數),採用定義好的函數factor 35 def sumFactors(a, b): 36 return sum(factor, a, b) 37 38 print("1+2+3+4+5=%d" % sumInts(1, 5)) 39 print("1^2+2^2+3^2+4^2+5^2=%d" % sumSquares(1, 5)) 40 print("1!+2!+3!+4!+5!=%d" % sumFactors(1, 5))
從上面解決同一個問題的代碼量比較來看,Scala和Python比較短,而Java比較長,而且Java對函數式編程的支持目前還比較有限,因此函數式編程建議採用Scala或者Python。