函數式編程簡介
這要看您問的是誰, 函數式編程 (FP)要麼是一種理念先進的、應該廣泛傳播的程序設計方法;要麼是一種偏學術性的、實際用途不多的編程方式。在這篇文章中我將講解函數式編程,探究其優點,並推薦學習函數式編程的資源。
語法入門
本文的代碼示例使用的是 Haskell 編程語言。在這篇文章中你只需要了解的基本函數語法:
even :: Int -> Bool
even = ... -- 具體的實現放在這裡
上述示例定義了含有一個參數的函數 even
,第一行是 類型聲明,具體來說就是 even
函數接受一個 Int 類型的參數,返回一個 Bool 類型的值,其實現跟在後面,由一個或多個等式組成。在這裡我們將忽略具體實現方法(名稱和類型已經足夠了):
map :: (a -> b) -> [a] -> [b]
map = ...
這個示例,map
是一個有兩個參數的函數:
(a -> b)
:將a
轉換成b
的函數[a]
:一個a
的列表,並返回一個b
的列表。(LCTT 譯註: 將函數作用到[a]
(List 序列對應於其它語言的數組)的每一個元素上,將每次所得結果放到另一個[b]
,最後返回這個結果[b]
。)
同樣我們不去關心要如何實現,我們只感興趣它的定義類型。a
和 b
是任何一種的的 類型變數 。就像上一個示例中, a
是 Int
類型, b
是 Bool
類型:
map even [1,2,3]
這個是一個 Bool 類型的序列:
[False,True,False]
如果你看到你不理解的其他語法,不要驚慌;對語法的充分理解不是必要的。
函數式編程的誤區
我們先來解釋一下常見的誤區:
- 函數式編程不是命令行編程或者面向對象編程的競爭對手或對立面,這並不是非此即彼的。
- 函數式編程不僅僅用在學術領域。這是真的,在函數式編程的歷史中,如像 Haskell 和 OCaml 語言是最流行的研究語言。但是今天許多公司使用函數式編程來用於大型的系統、小型專業程序,以及種種不同場合。甚至還有一個[面向函數式編程的商業用戶33的年度會議;以前的那些程序讓我們了解了函數式編程在工業中的用途,以及誰在使用它。
- 函數式編程與 monad 無關 ,也不是任何其他特殊的抽象。在這篇文章裡面 monad 只是一個抽象的規定。有些是 monad,有些不是。
- 函數式編程不是特別難學的。某些語言可能與您已經知道的語法或求值語義不同,但這些差異是淺顯的。函數式編程中有大量的概念,但其他語言也是如此。
什麼是函數式編程?
核心是函數式編程是只使用純粹的數學函數編程,函數的結果僅取決於參數,而沒有副作用,就像 I/O 或者狀態轉換這樣。程序是通過 組合函數 的方法構建的:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(g . f) x = g (f x)
這個 中綴 函數 (.)
表示的是二個函數組合成一個,將 g
作用到 f
上。我們將在下一個示例中看到它的使用。作為比較,我們看看在 Python 中同樣的函數:
def compose(g, f):
return lambda x: g(f(x))
函數式編程的優點在於:由於函數是確定的、沒有副作用的,所以可以用結果替換函數,這種替代等價於使用使 等式推理 。每個程序員都有使用自己代碼和別人代碼的理由,而等式推理就是解決這樣問題不錯的工具。來看一個示例。等你遇到這個問題:
map even . map (+1)
這段代碼是做什麼的?可以簡化嗎?通過等式推理,可以通過一系列替換來分析代碼:
map even . map (+1)
map (even . (+1)) -- 來自 'map' 的定義
map (x -> even (x + 1)) -- lambda 抽象
map odd -- 來自 'even' 的定義
我們可以使用等式推理來理解程序並優化可讀性。Haskell 編譯器使用等式推理進行多種程序優化。沒有純函數,等式推理是不可能的,或者需要程序員付出更多的努力。
函數式編程語言
你需要一種編程語言來做函數式編程嗎?
在沒有 高階函數 (傳遞函數作為參數和返回函數的能力)、lambdas (匿名函數)和 泛型 的語言中進行有意義的函數式編程是困難的。 大多數現代語言都有這些,但在不同語言中支持函數式編程方面存在差異。 具有最佳支持的語言稱為 函數式編程語言 。 這些包括靜態類型的 Haskell、OCaml、F# 和 Scala ,以及動態類型的 Erlang 和 Clojure。
即使是在函數式語言里,可以在多大程度上利用函數編程有很大差異。有一個 類型系統 會有很大的幫助,特別是它支持 類型推斷 的話(這樣你就不用總是必須鍵入類型)。這篇文章中沒有詳細介紹這部分,但足以說明,並非所有的類型系統都是平等的。
與所有語言一樣,不同的函數的語言強調不同的概念、技術或用例。選擇語言時,考慮它支持函數式編程的程度以及是否適合您的用例很重要。如果您使用某些非 FP 語言,你仍然會受益於在該語言支持的範圍內的函數式編程。
不要打開陷阱之門
回想一下,函數的結果只取決於它的輸入。但是,幾乎所有的編程語言都有破壞這一原則的「功能」。空值、 實例類型 (instanceof
)、類型轉換、異常、 邊際效用 ,以及無盡循環的可能性都是陷阱,它打破等式推理,並削弱程序員對程序行為正確性的理解能力。(所有語言裡面,沒有任何陷阱的語言包括 Agda、Idris 和 Coq。)
幸運的是,作為程序員,我們可以選擇避免這些陷阱,如果我們受到嚴格的規範,我們可以假裝陷阱不存在。 這個方法叫做 輕率推理 。它不需要任何條件,幾乎任何程序都可以在不使用陷阱的情況下進行編寫,並且通過避免這些可以而獲得等式推理、可組合性和可重用性。
讓我們詳細討論一下。 這個陷阱破壞了等式推理,因為異常終止的可能性沒有反映在類型中。(你可以慶幸文檔中甚至沒有提到能拋出的異常)。但是沒有理由我們沒有一個可以包含所有故障模式的返回類型。
避開陷阱是語言特徵中出現很大差異的領域。為避免例外, 代數數據類型 可用於模型錯誤的條件下,就像:
-- new data type for results of computations that can fail
--
data Result e a = Error e | Success a
-- new data type for three kinds of arithmetic errors
--
data ArithError = DivByZero | Overflow | Underflow
-- integer division, accounting for divide-by-zero
--
safeDiv :: Int -> Int -> Result ArithError Int
safeDiv x y =
if y == 0
then Error DivByZero
else Success (div x y)
在這個例子中的權衡你現在必須使用 Result ArithError Int 類型,而不是以前的 Int 類型,但這也是解決這個問題的一種方式。你不再需要處理異常,而能夠使用輕率推理 ,總體來說這是一個勝利。
自由定理
大多數現代靜態類型語言具有 范型 (也稱為 參數多態性 ),其中函數是通過一個或多個抽象類型定義的。 例如,看看這個 List(序列)函數:
f :: [a] -> [a]
f = ...
Java 中的相同函數如下所示:
static <A> List<A> f(List<A> xs) { ... }
該編譯的程序證明了這個函數適用於類型 a
的任意選擇。考慮到這一點,採用輕率推理的方法,你能夠弄清楚該函數的作用嗎?知道類型有什麼幫助?
在這種情況下,該類型並不能告訴我們函數的功能(它可以逆轉序列、刪除第一個元素,或許多其它的操作),但它確實告訴了我們很多信息。只是從該類型,我們可以推演出該函數的定理:
- 定理 1 :輸出中的每個元素也出現於輸入中;不可能在輸入的序列
a
中添加值,因為你不知道a
是什麼,也不知道怎麼構造一個。 - 定理 2 :如果你映射某個函數到列表上,然後對其應用
f
,其等同於對映射應用f
。
定理 1 幫助我們了解代碼的作用,定理 2 對於程序優化提供了幫助。我們從類型中學到了這一切!其結果,即從類型中獲取有用的定理的能力,稱之為 參數化 。因此,類型是函數行為的部分(有時是完整的)規範,也是一種機器檢查機制。
現在你可以利用參數化了。你可以從 map
和 (.)
的類型或者下面的這些函數中發現什麼呢?
foo :: a -> (a, a)
bar :: a -> a -> a
baz :: b -> a -> a
學習功能編程的資源
也許你已經相信函數式編程是編寫軟體不錯的方式,你想知道如何開始?有幾種學習功能編程的方法;這裡有一些我推薦(我承認,我對 Haskell 偏愛):
- UPenn 的 CIS 194: 介紹 Haskell 是函數式編程概念和 Haskell 實際開發的不錯選擇。有課程材料,但是沒有講座(您可以用幾年前 Brisbane 函數式編程小組的 CIS 194 系列講座。
- 不錯的入門書籍有 《Scala 的函數式編程》 、 《Haskell 函數式編程思想》 , 和 《Haskell 編程原理》。
- Data61 FP 課程 (即 NICTA 課程)通過 類型驅動開發 來教授基礎的抽象概念和數據結構。這是十分困難,但收穫也是豐富的,其起源於培訓會,如果你有一名願意引導你函數式編程的程序員,你可以嘗試。
- 在你的工作學習中使用函數式編程書寫代碼,寫一些純函數(避免不確定性和異常的出現),使用高階函數和遞歸而不是循環,利用參數化來提高可讀性和重用性。許多人從體驗和實驗各種語言的美妙之處,開始走上了函數式編程之旅。
- 加入到你的地區中的一些函數式編程小組或者學習小組中,或者創建一個,也可以是參加一些函數式編程的會議(新的會議總是不斷的出現)。
總結
在本文中,我討論了函數式編程是什麼以及不是什麼,並了解到了函數式編程的優勢,包括等式推理和參數化。我們了解到在大多數編程語言中都有一些函數式編程功能,但是語言的選擇會影響受益的程度,而 Haskell 是函數式編程中語言最受歡迎的語言。我也推薦了一些學習函數式編程的資源。
函數式編程是一個豐富的領域,還有許多更深入(更神秘)的主題正在等待探索。我沒有提到那些具有實際意義的事情,比如:
- lenses 和 prisms (是一流的設置和獲取值的方式;非常適合使用嵌套數據);
- 定理證明(當你可以證明你的代碼正確時,為什麼還要測試你的代碼?);
- 延遲評估(讓您處理潛在的無數的數據結構);
- 分類理論(函數式編程中許多美麗實用的抽象的起源);
我希望你喜歡這個函數式編程的介紹,並且啟發你走上這個有趣和實用的軟體開發之路。
本文根據 CC BY 4.0 許可證發布。
(題圖: opensource.com)
作者簡介:
紅帽軟體工程師。對函數式編程,分類理論,數學感興趣。Crazy about jalapeños.
via: https://opensource.com/article/17/4/introduction-functional-programming
作者:Fraser Tweedale 譯者:MonkeyDEcho 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive