函數語言程式設計簡介

贊 回覆發表於2017-09-15

我們來解釋函數語言程式設計的什麼,它的優點是哪些,並且給出一些函數語言程式設計的學習資源。

這要看您問的是誰, 函數語言程式設計functional programming(FP)要麼是一種理念先進的、應該廣泛傳播的程式設計方法;要麼是一種偏學術性的、實際用途不多的程式設計方式。在這篇文章中我將講解函數語言程式設計,探究其優點,並推薦學習函數語言程式設計的資源。

語法入門

本文的程式碼示例使用的是 Haskell 程式語言。在這篇文章中你只需要瞭解的基本函式語法:

even :: Int -> Bool
even = ...    -- 具體的實現放在這裡

上述示例定義了含有一個引數的函式 even ,第一行是 型別宣告,具體來說就是 even 函式接受一個 Int 型別的引數,返回一個 Bool 型別的值,其實現跟在後面,由一個或多個等式組成。在這裡我們將忽略具體實現方法(名稱和型別已經足夠了):

map :: (a -> b) -> [a] -> [b]
map = ...

這個示例,map 是一個有兩個引數的函式:

  1. (a -> b) :將 a 轉換成 b 的函式
  2. [a]:一個 a 的列表,並返回一個 b 的列表。(LCTT 譯註: 將函式作用到 [a] (List 序列對應於其它語言的陣列)的每一個元素上,將每次所得結果放到另一個 [b] ,最後返回這個結果 [b]。)

同樣我們不去關心要如何實現,我們只感興趣它的定義型別。a 和 b 是任何一種的的 型別變數type variable 。就像上一個示例中, a 是 Int 型別, b 是 Bool 型別:

map even [1,2,3]

這個是一個 Bool 型別的序列:

[False,True,False]

如果你看到你不理解的其他語法,不要驚慌;對語法的充分理解不是必要的。

函數語言程式設計的誤區

我們先來解釋一下常見的誤區:

  • 函數語言程式設計不是命令列程式設計或者物件導向程式設計的競爭對手或對立面,這並不是非此即彼的。
  • 函數語言程式設計不僅僅用在學術領域。這是真的,在函數語言程式設計的歷史中,如像 Haskell 和 OCaml 語言是最流行的研究語言。但是今天許多公司使用函數語言程式設計來用於大型的系統、小型專業程式,以及種種不同場合。甚至還有一個[面向函數語言程式設計的商業使用者33的年度會議;以前的那些程式讓我們瞭解了函數語言程式設計在工業中的用途,以及誰在使用它。
  • 函數語言程式設計與 monad 無關 ,也不是任何其他特殊的抽象。在這篇文章裡面 monad 只是一個抽象的規定。有些是 monad,有些不是。
  • 函數語言程式設計不是特別難學的。某些語言可能與您已經知道的語法或求值語義不同,但這些差異是淺顯的。函數語言程式設計中有大量的概念,但其他語言也是如此。

什麼是函數語言程式設計?

核心是函數語言程式設計是隻使用純粹的數學函式程式設計,函式的結果僅取決於引數,而沒有副作用,就像 I/O 或者狀態轉換這樣。程式是通過 組合函式function composition 的方法構建的:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(g . f) x = g (f x)

這個中綴infix函式 (.) 表示的是二個函式組合成一個,將 g 作用到 f 上。我們將在下一個示例中看到它的使用。作為比較,我們看看在 Python 中同樣的函式:

def compose(g, f):
  return lambda x: g(f(x))

函數語言程式設計的優點在於:由於函式是確定的、沒有副作用的,所以可以用結果替換函式,這種替代等價於使用使 等式推理equational reasoning 。每個程式設計師都有使用自己程式碼和別人程式碼的理由,而等式推理就是解決這樣問題不錯的工具。來看一個示例。等你遇到這個問題:

map even . map (+1)

這段程式碼是做什麼的?可以簡化嗎?通過等式推理,可以通過一系列替換來分析程式碼:

map even . map (+1)
map (even . (+1))         -- 來自 'map' 的定義
map (\x -> even (x + 1))  -- lambda 抽象
map odd                   -- 來自 'even' 的定義

我們可以使用等式推理來理解程式並優化可讀性。Haskell 編譯器使用等式推理進行多種程式優化。沒有純函式,等式推理是不可能的,或者需要程式設計師付出更多的努力。

函數語言程式設計語言

你需要一種程式語言來做函數語言程式設計嗎?

在沒有高階函式higher-order function(傳遞函式作為引數和返回函式的能力)、lambdas (匿名函式)和泛型generics的語言中進行有意義的函數語言程式設計是困難的。 大多數現代語言都有這些,但在不同語言中支援函數語言程式設計方面存在差異。 具有最佳支援的語言稱為函數語言程式設計語言functional programming language。 這些包括靜態型別的 HaskellOCamlF#Scala ,以及動態型別的 ErlangClojure

即使是在函式式語言裡,可以在多大程度上利用函式程式設計有很大差異。有一個型別系統type system會有很大的幫助,特別是它支援 型別推斷type inference 的話(這樣你就不用總是必須鍵入型別)。這篇文章中沒有詳細介紹這部分,但足以說明,並非所有的型別系統都是平等的。

與所有語言一樣,不同的函式的語言強調不同的概念、技術或用例。選擇語言時,考慮它支援函數語言程式設計的程度以及是否適合您的用例很重要。如果您使用某些非 FP 語言,你仍然會受益於在該語言支援的範圍內的函數語言程式設計。

不要開啟陷阱之門

回想一下,函式的結果只取決於它的輸入。但是,幾乎所有的程式語言都有破壞這一原則的“功能”。空值、例項型別type caseinstanceof)、型別轉換、異常、邊際效用side-effect,以及無盡迴圈的可能性都是陷阱,它打破等式推理,並削弱程式設計師對程式行為正確性的理解能力。(所有語言裡面,沒有任何陷阱的語言包括 Agda、Idris 和 Coq。)

幸運的是,作為程式設計師,我們可以選擇避免這些陷阱,如果我們受到嚴格的規範,我們可以假裝陷阱不存在。 這個方法叫做輕率推理fast and loose reasoning 。它不需要任何條件,幾乎任何程式都可以在不使用陷阱的情況下進行編寫,並且通過避免這些可以而獲得等式推理、可組合性和可重用性。

讓我們詳細討論一下。 這個陷阱破壞了等式推理,因為異常終止的可能性沒有反映在型別中。(你可以慶幸文件中甚至沒有提到能丟擲的異常)。但是沒有理由我們沒有一個可以包含所有故障模式的返回型別。

避開陷阱是語言特徵中出現很大差異的領域。為避免例外, 代數資料型別algebraic data type可用於模型錯誤的條件下,就像:

-- 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 型別,但這也是解決這個問題的一種方式。你不再需要處理異常,而能夠使用輕率推理 ,總體來說這是一個勝利。

自由定理

大多數現代靜態型別語言具有範型generics(也稱為引數多型性parametric polymorphism ),其中函式是通過一個或多個抽象型別定義的。 例如,看看這個 List(序列)函式:

f :: [a] -> [a]
f = ...

Java 中的相同函式如下所示:

static <A> List<A> f(List<A> xs) { ... }

該編譯的程式證明了這個函式適用於型別 a任意選擇。考慮到這一點,採用輕率推理的方法,你能夠弄清楚該函式的作用嗎?知道型別有什麼幫助?

在這種情況下,該型別並不能告訴我們函式的功能(它可以逆轉序列、刪除第一個元素,或許多其它的操作),但它確實告訴了我們很多資訊。只是從該型別,我們可以推演出該函式的定理:

  • 定理 1 :輸出中的每個元素也出現於輸入中;不可能在輸入的序列 a 中新增值,因為你不知道 a 是什麼,也不知道怎麼構造一個。
  • 定理 2 :如果你對映某個函式到列表上,然後對其應用 f,其等同於對對映應用 f

定理 1 幫助我們瞭解程式碼的作用,定理 2 對於程式優化提供了幫助。我們從型別中學到了這一切!其結果,即從型別中獲取有用的定理的能力,稱之為引數化parametricity。因此,型別是函式行為的部分(有時是完整的)規範,也是一種機器檢查機制。

現在你可以利用引數化了。你可以從 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 課程)通過型別驅動開發type-driven development來教授基礎的抽象概念和資料結構。這是十分困難,但收穫也是豐富的,其起源於培訓會,如果你有一名願意引導你函數語言程式設計的程式設計師,你可以嘗試。
  • 在你的工作學習中使用函數語言程式設計書寫程式碼,寫一些純函式(避免不確定性和異常的出現),使用高階函式和遞迴而不是迴圈,利用引數化來提高可讀性和重用性。許多人從體驗和實驗各種語言的美妙之處,開始走上了函數語言程式設計之旅。
  • 加入到你的地區中的一些函數語言程式設計小組或者學習小組中,或者建立一個,也可以是參加一些函數語言程式設計的會議(新的會議總是不斷的出現)。

總結

在本文中,我討論了函數語言程式設計是什麼以及不是什麼,並瞭解到了函數語言程式設計的優勢,包括等式推理和引數化。我們瞭解到在大多數程式語言中都有一些函數語言程式設計功能,但是語言的選擇會影響受益的程度,而 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

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章