Lambda Conf上有人講C++函數式編程。
在Functional Conf 2019上,就有主題為“Lambdas: The Functional Programming Companion of Modern C++”的演講。
演講者介紹了現代C++中函數式編程相關內容,講解了如何使用Lambda表達式編寫符合函數式編程原則的C++代碼,以及C++語言的演變等內容。
NDC Conferences上也有相關演講,主題為“Functional C++ for Fun & Profit”。
演講探討了函數式編程與C++的交集,從C++11中Lambda表達式的引入開始,研究了函數式編程的核心原則,以及如何在C++開發中有效應用這些原則。
?
函數式編程
?
函數式編程是一種編程范式,核心思想是將計算過程視為數學函數的組合,強調函數的純粹性和數據的不可變性,避免副作用(如修改外部狀態、改變變量值等)。
特點包括:
- 不可變數據:數據一旦創建就不能被修改,若需變更,需生成新的數據副本,減少狀態混亂。
- 純函數:輸入決定輸出,不依賴外部狀態,也不產生副作用(如修改全局變量、IO操作等),相同輸入始終返回相同結果,便于測試和并行計算。
- 函數是一等公民:函數可以像變量一樣被傳遞、賦值、作為參數或返回值,支持高階函數(如接收或返回其他函數)。
- 避免狀態變化:通過函數組合而非循環或狀態修改來實現邏輯,常用遞歸代替迭代。
例如,在處理列表時,函數式編程會用?map?(映射)、?filter?(過濾)等純函數組合操作,而非通過循環修改列表元素。
常見的函數式編程語言有Haskell、Scala、Erlang等,現代主流語言(如Java、C++、Python)也逐漸引入了函數式特性(如Lambda表達式、不可變集合)。
?
代碼示例
?
用現代C++特性(Lambda、算法庫)處理數據,體現“用函數組合實現邏輯”“避免修改狀態”的特點:
#include <iostream>
#include <vector>
#include <algorithm> // 提供函數式算法
#include <numeric> ? // 提供accumulate
int main() {
// 原始數據(不可修改,體現不可變性)
const std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
? ? // 1. 過濾:保留偶數(用lambda作為篩選條件)
std::vector<int> evens;
std::copy_if(numbers.begin(), numbers.end(),?
std::back_inserter(evens),
[](int n) { return n % 2 == 0; }); // 純函數:輸入決定輸出
? ? // 2. 轉換:偶數乘以2(用lambda映射)
std::vector<int> doubled;
std::transform(evens.begin(), evens.end(),
std::back_inserter(doubled),
[](int n) { return n * 2; }); // 無副作用的轉換
? ? // 3. 聚合:計算總和(用accumulate組合結果)
int sum = std::accumulate(doubled.begin(), doubled.end(),
0, // 初始值
[](int total, int n) { return total + n; });
? ? // 輸出結果:2+4+6+8+10的兩倍之和 → (2+4+6+8+10)*2 = 60
std::cout << "結果: " << sum << std::endl; // 僅此處有副作用(IO)
return 0;
}
函數式特點說明:
- 不可變數據:?numbers?被聲明為?const?,全程不修改原始數據。
- 純函數:所有Lambda表達式(如?n%2==0?、?n*2?)都只依賴輸入,無外部狀態。
- 函數作為參數:?copy_if?、?transform?等算法接收Lambda函數作為參數,體現“函數是一等公民”。
- 無顯式循環:用標準庫算法替代?for?循環,邏輯更接近“做什么”而非“怎么做”。
運行結果為?60?,整個過程通過函數組合完成數據處理,避免了手動修改變量狀態。
?
優缺點
?
好處
?
1.?代碼更易理解和維護
純函數無副作用,邏輯獨立,相同輸入始終返回相同結果,代碼意圖更清晰,減少了因狀態變化導致的“隱藏邏輯”。
2.?天然支持并發和并行
不可變數據避免了多線程中的“資源競爭”(無需加鎖),純函數可安全地在多線程中并行執行,適合分布式和高性能場景。
3.?便于測試和調試
純函數不依賴外部狀態,單元測試時無需復雜的環境準備,只需驗證輸入輸出;調試時也無需追蹤變量的狀態變化歷史。
4.?代碼復用性強
函數作為“一等公民”可被靈活組合(如通過?map?/?filter?/?reduce?),形成新的功能,減少重復代碼。
5.?減少錯誤
不可變性避免了意外修改數據導致的bug(如“一個地方改了,別處跟著錯”),尤其在復雜系統中更明顯。
?
壞處
?
1.?學習曲線較陡
思維方式與常見的命令式編程(如用?for?循環、修改變量)差異大,理解遞歸、函數組合、Monad等概念需要時間。
2.?性能開銷可能更高
不可變數據每次修改需創建副本,對于大型數據(如大列表)可能增加內存占用和計算成本(盡管現代語言有優化)。
3.?處理IO等副作用時較繁瑣
純函數禁止IO(如讀寫文件、網絡請求),需通過特殊機制(如Haskell的IO Monad)封裝副作用,代碼可能更復雜。
4.?不適合所有場景
對于需要頻繁修改狀態的場景(如GUI交互、游戲幀更新),函數式的“不可變”思路可能反直覺,實現效率低。
5.?調試棧可能更復雜
大量函數組合和遞歸可能導致調用棧較深,調試時追蹤問題源頭相對麻煩。
?
總結
?
函數式編程適合復雜業務邏輯、高并發、分布式系統等場景,能提升代碼可靠性和可維護性;但在性能敏感、IO密集、狀態頻繁變化的場景中,可能不如命令式編程直接高效。現代主流語言(如C++、Python)多采用“混合范式”,兼顧兩者優勢。