App下載

了解一下函數(shù)式編程,送給沉迷面向?qū)ο蟮某绦騿T

猿友 2020-08-11 17:11:04 瀏覽數(shù) (2827)
反饋

今天給大家講解一下函數(shù)式編程的小知識。函數(shù)式編程已經(jīng)存在了60多年,但是到目前為止,它一直都很小眾。只有像Google這樣的改變游戲規(guī)則的企業(yè)才會依賴函數(shù)式編程,普通程序員對此幾乎一無所知。

這種情況很快就要被改變了。像JavaPython這樣的語言已經(jīng)開始越來越多地開始采用函數(shù)編程,而像Haskell這樣的新語言已經(jīng)完全融入了函數(shù)式編程

簡單來說,函數(shù)式編程就是為不可變變量構(gòu)建函數(shù)。相反,面向?qū)ο蟮木幊淌且哂幸唤M相對固定的函數(shù),而我們主要是在修改或添加新變量。

函數(shù)式編程具有非常適合諸如數(shù)據(jù)分析和機(jī)器學(xué)習(xí)之類的需求任務(wù)的特性。但是這并不意味著我們應(yīng)該告別面向?qū)ο缶幊?/code>,轉(zhuǎn)而完全使用函數(shù)式編程。我們需要了解其中的基本原理,這樣我們就能在適當(dāng)?shù)臅r候使用它們。

(推薦教程:傻瓜函數(shù)式編程

一切都是為了消除副作用

要了解函數(shù)式編程,我們需要首先了解函數(shù)。 這聽起來可能很無聊,但總而言之,它很有見地。

簡單地說,函數(shù)是將輸入轉(zhuǎn)換為輸出的東西。只是事情并沒有那么簡單。思考一下,在Python中的下面這個函數(shù)的意義:

def square(x):
    return x*x

這個函數(shù)很簡單。 它需要一個變量x,可能是一個int,或者是一個 floatdouble,然后輸出該變量的平方。

再思考一下下面的這個函數(shù):

global_list = []
def append_to_list(x):
    global_list.append(x)

乍一看,這個函數(shù)接受了一個變量 x,無論是哪種類型,由于沒有 return 語句,它什么也不返回。事實(shí)真的是這樣嗎?

如果事先沒有定義 global_list,那么這個函數(shù)就不能工作,它的輸出是相同的列表,盡管經(jīng)過了修改。雖然 global_list 沒有聲明輸入,但當(dāng)我們使用該函數(shù)時,它就會發(fā)生變化:

append_to_list(1)
append_to_list(2)
global_list

它返回了 [1,2],而不是空列表。這可能就是問題所在,列表確實(shí)是函數(shù)的一個輸入,雖然我們沒有明確說明。

1.不忠于函數(shù)

這些隱含的輸入,或者其他情況下的輸出,有一個官方名稱:副作用。雖然我們只列舉了一個簡單的例子,但在更復(fù)雜的程序中,這些可能會讓我們面臨真正的困難。

大家可以思考一下該如何測試 append_to_list:我們不僅需要閱讀第一行并使用任何 x 來測試函數(shù),還需要閱讀整個定義,了解其作用,定義 global_list 并以這種方式進(jìn)行測試。這個例子告訴我們,當(dāng)你在處理有數(shù)千行代碼的程序時,簡單的東西很快就會變得乏味。

好消息是,有一個簡單的解決方法:對函數(shù)作為輸入的內(nèi)容誠實(shí)。這樣更好:

newlist = []
def append_to_list2(x, some_list):
    some_list.append(x)
append_to_list2(1,newlist)
append_to_list2(2,newlist)
newlist

我們并沒有作太大的改變,輸出結(jié)果仍然是 [1,2],其他所有內(nèi)容也保持不變。

但是,我們已經(jīng)更改了一件事情:該代碼現(xiàn)在沒有副作用。

現(xiàn)在,當(dāng)我們查看函數(shù)聲明時,能確切知道發(fā)生了什么。如果程序運(yùn)行不正常,我們也可以輕松地單獨(dú)測試每個功能并查明哪個功能有問題。

2.函數(shù)式編程正在編寫純函數(shù)

具有明確聲明的輸入和輸出的函數(shù)是沒有副作用的函數(shù),而沒有副作用的函數(shù)就是純函數(shù)。

函數(shù)編程的一個非常簡單的定義是:僅用純函數(shù)編寫程序。純函數(shù)永遠(yuǎn)不會修改變量,只會創(chuàng)建新的變量作為輸出。

此外,對于給定輸入的純函數(shù),我們可以得到特定的輸出。相反,不純函數(shù)可能依賴于某些全局變量。因此,如果全局變量不同,則相同的輸入變量可能導(dǎo)致不同的輸出。后者會讓調(diào)試和代碼維護(hù)變得更加困難。

這里有一個容易發(fā)現(xiàn)副作用的簡單規(guī)則:由于每個函數(shù)必須具有某種輸入和輸出,因此沒有任何輸入或輸出的函數(shù)聲明必須是不純的。如果采用函數(shù)式編程,這是你可能想要更改的第一個聲明。

函數(shù)式編程不僅是 map 和 reduce

循環(huán)不是函數(shù)式編程中的東西。首先,我們先來思考以下的Python循環(huán):

integers = [1,2,3,4,5,6]
odd_ints = []
squared_odds = []
total = 0
for i in integers:
    if i%2 ==1
        odd_ints.append(i)
for i in odd_ints:
    squared_odds.append(i*i)
for i in squared_odds:
    total += i

相較于我們要執(zhí)行的簡單操作,以上代碼明顯過長。而且也沒有起到作用,因?yàn)槲覀冋谛薷娜肿兞俊?/p>

相反,我們可以用以下代碼替代:

from functools import reduce
integers = [1,2,3,4,5,6]
odd_ints = filter(lambda n: n % 2 == 1, integers)
squared_odds = map(lambda n: n * n, odd_ints)
total = reduce(lambda acc, n: acc + n, squared_odds)

這是完整的函數(shù)式。它比較短,也更快,因?yàn)槲覀儾恍枰嗟臄?shù)組元素。如果你理解 filter, mapreduce 如何工作,代碼也就不難理解了。

這并不意味著所有的函數(shù)代碼都使用 map、reduce 等。這也不意味著你需要函數(shù)式編程來理解 mapreduce。只是當(dāng)你抽象循環(huán)時,這些函數(shù)會彈出很多。

1.Lambda函數(shù)

在談到函數(shù)式編程的歷史時,許多人都是從lambda函數(shù)的發(fā)明開始的。 盡管 lambda 是函數(shù)式編程毫無疑問的基石,但它們并不是根本原因。

Lambda 函數(shù)是可用于使程序起作用的工具。 但是,我們也可以在面向?qū)ο蟮木幊讨惺褂?code>lambda。

2.靜態(tài)類型

上面的示例雖然不是靜態(tài)類型的,但是它依然是函數(shù)式的。

即使靜態(tài)類型為我們的代碼增加了一層額外的安全保護(hù),但是其函數(shù)正常也并非必不可少。 不過,這可能是一個不錯的補(bǔ)充。

有些編程語言的函數(shù)式編程越來越強(qiáng)

1.Perl

Perl 對副作用的處理方法與大多數(shù)編程語言截然不同。它包含了一個神奇的參數(shù) $\ 。Perl確實(shí)有它的優(yōu)點(diǎn),但我不會用它進(jìn)行函數(shù)式編程。

(推薦教程:Perl教程

2.Java

如果你在用 Java 進(jìn)行函數(shù)式編程,那我只能祝你好運(yùn)了。因?yàn)槟愕某绦蛴幸话胧怯伸o態(tài)關(guān)鍵字組成的,而且其他 Java 開發(fā)人員也會把你的程序視為恥辱。

這并不是說 Java 有多糟糕,而是因?yàn)樗⒉皇菫槟切┯煤瘮?shù)式編程解決問題而設(shè)計的,比如數(shù)據(jù)庫管理或機(jī)器學(xué)習(xí)應(yīng)用程序。

(推薦教程:Java教程

3.Scala

有趣的是:Scala 的目標(biāo)是統(tǒng)一面向?qū)ο蠛秃瘮?shù)式編程。如果你覺得這有點(diǎn)奇怪,那你不是一個人,因?yàn)樗腥硕歼@么覺得:函數(shù)式編程的目標(biāo)是完全消除副作用,而面向?qū)ο缶幊淌前迅弊饔帽A粼趯ο髢?nèi)部。

盡管如此,很多開發(fā)人員認(rèn)為 Scala 是一種幫助他們從面向?qū)ο缶幊踢^渡到函數(shù)式編程的語言?;蛟S在未來幾年里,它們會更容易全面發(fā)揮作用。

(推薦教程:Scala教程

4.Python

Python 鼓勵函數(shù)式編程。一個事實(shí)就能看到這一點(diǎn):每個函數(shù)在默認(rèn)情況下至少有一個輸入self。這很像Python的禪:顯式比隱式好!

(推薦教程:python教程

5.Clojure

據(jù)它的創(chuàng)建者說,Clojure 大約有 80% 是函數(shù)式編程。默認(rèn)情況下,所有值都是不可變的,就像在函數(shù)式編程中需要它們一樣。但是,我們可以通過在這些不可變的值周圍使用可變值包裝器來解決這個問題。當(dāng)你打開這樣一個包裝,你得到的東西又是不變的。

(推薦教程:Clojure教程

6.Haskell

這是為數(shù)不多的純函數(shù)式和靜態(tài)類型的語言之一。雖然在開發(fā)過程中這看起來像是一個時間消耗器,但在調(diào)試程序時,Haskell會付出巨大的代價。它不像其他語言那么容易學(xué),但絕對值得投資!

(推薦教程:Real World Haskell 中文版

大數(shù)據(jù)時代帶來了函數(shù)式編程

與面向?qū)ο缶幊滔啾?,函?shù)式編程仍然是一個新生兒。但是如果在 Python 和其他語言中包含函數(shù)式編程原理,具有不一樣的意義,那么函數(shù)式編程就有可能獲得關(guān)注。

函數(shù)式編程對于大型數(shù)據(jù)庫、并行編程和機(jī)器學(xué)習(xí)非常有用。在過去的十年里,所有這些都在蓬勃發(fā)展。

雖然面向?qū)ο蟠a有著不可估量的優(yōu)點(diǎn),但函數(shù)代碼的優(yōu)點(diǎn)卻不容忽視。只需要學(xué)習(xí)一些基本原理,就足以讓我們成為一名開發(fā)人員,并為未來做好準(zhǔn)備。

以上就是關(guān)于函數(shù)式編程的相關(guān)介紹了,希望對大家有所幫助。

0 人點(diǎn)贊