OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers
什麼是函數式編程(FP,Functional Programming),目前彷佛並無一個讓你們廣泛承認的定義,今天在這裏也不打算討論這個問題。本文的目的就是帶你們感覺一下函數式編程的感受,並非一個函數式編程的教程。java
咱們打算從一個簡單例子帶你們走進函數式編程的世界,我打算從三種不一樣的語言來介紹,若是讀者不熟悉某門語言的話,或者只想閱讀本身熟悉的語言,徹底能夠跳過不感興趣的語言,我會盡可能把每一種語言當成一個獨立的單元來說。python
由於只是帶你們體驗一下,咱們選取的題目相對來講比較簡單。編程
輸入一系列文件,須要咱們統計每一個文件的行數。函數式編程
爲了簡化問題,這裏咱們只去統計每一個文件中換行符\n
的個數,假定最後一行也有一個換行符(固然,你也能夠直接加上1
獲得最後的真實行數)。函數
若是以傳統命令式的編程方式,大概須要進行如下的分析0
. 定義一個文件行數統計列表,用來保存每一個文件的行數;1
. 循環打開文件;2
. 定義一個計數器變量count
;3
. 讀取文件字符,判斷是否爲回車符\n
,是則將count
加1
;4
. 當前文件讀取結束,存儲當前文件的行數;5
. 循環結束,返回統計結果。
上面的思惟流程很是清晰明瞭,也很是符合咱們日常的開發流程,下面咱們進入到各個語言中去,看看從函數式的角度去看看如何解決。code
本例中咱們用vector<string>
來表示輸入的文件集合,用vector<int>
來表示對應的文件行數。orm
這裏咱們先以命令式的方式實現這個需求,以便你們更加直觀的感覺其與函數式思惟的區別教程
std::vector<int> count_lines_in_files(const std::vector<std::string>& files) { std::vector<int> lines; char c = 0; for (const auto& file : files) { int count = 0; std::ifstream in{file}; while (in.get(c)) { if (c == '\n') count++; } lines.push_back(count); } return lines; }
咱們定義一個輔助函數open_file
來轉換文件到文件流。接口
std::ifstream open_file(std::string file) { return std::ifstream{file}; }
而後統計文件流當中的換行符\n
個數內存
int count_lines(std::ifstream in) { return std::count(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>(), '\n'); }
最後咱們分別將上面的轉換函數open_file
和統計函數count_lines
分別映射到輸入的文件的列表,在C++
語言中,映射的操做就是std::transform
,在python
和Java
中就是map
函數,這類函數在函數式編程中被稱爲高階函數,他們能夠接受函數做爲形參,將函數視爲一等公民。
std::vector<int> count_lines_in_files(const std::vector<std::string>& files) { std::vector<int> lines(files.size()); std::vector<std::ifstream> filestreams(files.size()); std::transform(std::begin(files), std::end(files), std::begin(filestreams), open_file); std::transform(std::begin(filestreams), std::end(filestreams), std::begin(lines), count_lines); return lines; }
上面的這種解決方法,咱們沒有去關心如何打開文件,以及統計是如何進行的。
上述的程式咱們只是告訴計算機咱們但願在給定的流中去統計換行符\n
,這裏咱們將統計換行符這個動做count_lines
封裝起來,就是想說明咱們不關心count_lines
這個動做,此次是統計換行符\n
,下次能夠是統計任意的字符或者單詞,everything
,只要符合咱們的接口契約就能夠。
函數式編程的主要思想 —— 使用抽象去代表咱們的目的,而不是說明如何去作,只需指明輸入轉換爲指望的輸出。
上面的程序,咱們能夠range
和range transformations
來實現(這個是C++20
才引入的),函數的意圖將會更加清晰明瞭。
std::vector<int> count_lines_in_files(const std::vector<std::string>& files) { return files | transform(open_file) | transform(count_lines); }
這裏range
使用管道|
操做符表示經過轉換來傳遞一個集合,有興趣的讀者能夠去研究一下。
本例中咱們能夠用List<File>
來表示輸入的文件集合,而後構造一個List<Long>
來表示對應的文件行數。
關於命令式的解答這裏就不在演示了,有興趣的讀者能夠本身嘗試。
下面咱們看看java
如何經過函數式思惟解決這個問題,咱們先定義一個統計函數countLine
用來統計文件中換行符\n
的個數。
static long countLine(File file) { long count = 0; try (Stream<String> lines = Files.lines(Paths.get(file.toURI()), Charset.defaultCharset())) { count = lines.map(line -> Arrays.stream(line.split("\n"))).count(); } catch (IOException e) { } return count; }
有了這個輔助函數,咱們利用Java8
的stream
特性結合映射操做map
,將輸入的文件列表映射到統計函數countLine
,最後使用收集器執行終端操做。
public static List<Long> countFilesLine(List<File> files) { return files.stream() .map(LinesCounter::countLine) .collect(toList()); }
上述LinesCounter::countLine
寫法被稱爲方法引用,若是對stream
或者方法引用不熟悉的讀者能夠參考Richard Warburton
所著的java 8 函數式編程
一書。
因爲python
語言自身的特性,不想C++
和Java
對類型要求嚴格,本例中輸入文件列表和輸出文件行數咱們均可以使用python
內置的list
數據類型來表達。
這個例子對於python語言比較簡單,咱們故意使用下面的方式去解答這個問題,真正的時候咱們可能不會去這麼作。
def open_file(file): with open(file, 'r') as f: return f.read() def count_line(): return lambda file: open_file(file).count('\n') def count_lines_in_files(files): return list(map(count_line(), files))
固然咱們能夠利用了python
的readlines
返回文件全部行的列表,而後經過映射操做計算列表長度便可。
def read_lines(file): with open(file) as fin: return fin.readlines() def count_lines_in_files(files): return list(map(len, map(read_lines, files)))
不過,上面的方法有個弊端,因爲readlines()方法讀取整個文件全部行,保存爲列表,當文件過大的時候會佔據過大內存。
雖然這裏我介紹了一些FP
的優勢,但並不表示命令式就一無可取,相反,本人是OO
的堅實擁護者,同時也是FP
的粉絲,我的更加傾向於多範式的結合編程。
看完這個例子,有沒有激起你對函數式編程的興趣,如有,抓緊行動起來吧…