今天給大家講解一下函數(shù)式編程
的小知識。函數(shù)式編程
已經(jīng)存在了60多年,但是到目前為止,它一直都很小眾。只有像Google
這樣的改變游戲規(guī)則的企業(yè)才會依賴函數(shù)式編程
,普通程序員對此幾乎一無所知。
這種情況很快就要被改變了。像Java
和Python
這樣的語言已經(jīng)開始越來越多地開始采用函數(shù)編程
,而像Haskell
這樣的新語言已經(jīng)完全融入了函數(shù)式編程
。
簡單來說,函數(shù)式編程
就是為不可變變量構(gòu)建函數(shù)。相反,面向?qū)ο蟮木幊淌且哂幸唤M相對固定的函數(shù),而我們主要是在修改或添加新變量。
函數(shù)式編程
具有非常適合諸如數(shù)據(jù)分析和機器學習之類的需求任務(wù)的特性。但是這并不意味著我們應(yīng)該告別面向?qū)ο缶幊?/code>,轉(zhuǎn)而完全使用
函數(shù)式編程
。我們需要了解其中的基本原理,這樣我們就能在適當?shù)臅r候使用它們。
(推薦教程:傻瓜函數(shù)式編程)
一切都是為了消除副作用
要了解函數(shù)式編程,我們需要首先了解函數(shù)。 這聽起來可能很無聊,但總而言之,它很有見地。
簡單地說,函數(shù)是將輸入轉(zhuǎn)換為輸出的東西。只是事情并沒有那么簡單。思考一下,在Python
中的下面這個函數(shù)的意義:
def square(x):
return x*x
這個函數(shù)很簡單。 它需要一個變量x
,可能是一個int
,或者是一個 float
或 double
,然后輸出該變量的平方。
再思考一下下面的這個函數(shù):
global_list = []
def append_to_list(x):
global_list.append(x)
乍一看,這個函數(shù)接受了一個變量 x
,無論是哪種類型,由于沒有 return
語句,它什么也不返回。事實真的是這樣嗎?
如果事先沒有定義 global_list
,那么這個函數(shù)就不能工作,它的輸出是相同的列表,盡管經(jīng)過了修改。雖然 global_list
沒有聲明輸入,但當我們使用該函數(shù)時,它就會發(fā)生變化:
append_to_list(1)
append_to_list(2)
global_list
它返回了 [1,2]
,而不是空列表。這可能就是問題所在,列表確實是函數(shù)的一個輸入,雖然我們沒有明確說明。
1.不忠于函數(shù)
這些隱含的輸入,或者其他情況下的輸出,有一個官方名稱:副作用。雖然我們只列舉了一個簡單的例子,但在更復雜的程序中,這些可能會讓我們面臨真正的困難。
大家可以思考一下該如何測試 append_to_list
:我們不僅需要閱讀第一行并使用任何 x
來測試函數(shù),還需要閱讀整個定義,了解其作用,定義 global_list
并以這種方式進行測試。這個例子告訴我們,當你在處理有數(shù)千行代碼的程序時,簡單的東西很快就會變得乏味。
好消息是,有一個簡單的解決方法:對函數(shù)作為輸入的內(nèi)容誠實。這樣更好:
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)在,當我們查看函數(shù)聲明時,能確切知道發(fā)生了什么。如果程序運行不正常,我們也可以輕松地單獨測試每個功能并查明哪個功能有問題。
2.函數(shù)式編程正在編寫純函數(shù)
具有明確聲明的輸入和輸出的函數(shù)是沒有副作用的函數(shù),而沒有副作用的函數(shù)就是純函數(shù)。
函數(shù)編程的一個非常簡單的定義是:僅用純函數(shù)編寫程序。純函數(shù)永遠不會修改變量,只會創(chuàng)建新的變量作為輸出。
此外,對于給定輸入的純函數(shù),我們可以得到特定的輸出。相反,不純函數(shù)可能依賴于某些全局變量。因此,如果全局變量不同,則相同的輸入變量可能導致不同的輸出。后者會讓調(diào)試和代碼維護變得更加困難。
這里有一個容易發(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í)行的簡單操作,以上代碼明顯過長。而且也沒有起到作用,因為我們正在修改全局變量。
相反,我們可以用以下代碼替代:
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ù)式。它比較短,也更快,因為我們不需要迭代太多的數(shù)組元素。如果你理解 filter
, map
和 reduce
如何工作,代碼也就不難理解了。
這并不意味著所有的函數(shù)代碼都使用 map
、reduce
等。這也不意味著你需要函數(shù)式編程來理解 map
和 reduce
。只是當你抽象循環(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)類型為我們的代碼增加了一層額外的安全保護,但是其函數(shù)正常也并非必不可少。 不過,這可能是一個不錯的補充。
有些編程語言的函數(shù)式編程越來越強
1.Perl
Perl
對副作用的處理方法與大多數(shù)編程語言截然不同。它包含了一個神奇的參數(shù) $\
。Perl
確實有它的優(yōu)點,但我不會用它進行函數(shù)式編程。
(推薦教程:Perl教程)
2.Java
如果你在用 Java
進行函數(shù)式編程,那我只能祝你好運了。因為你的程序有一半是由靜態(tài)關(guān)鍵字組成的,而且其他 Java
開發(fā)人員也會把你的程序視為恥辱。
這并不是說 Java
有多糟糕,而是因為它并不是為那些用函數(shù)式編程解決問題而設(shè)計的,比如數(shù)據(jù)庫管理或機器學習應(yīng)用程序。
(推薦教程:Java教程)
3.Scala
有趣的是:Scala
的目標是統(tǒng)一面向?qū)ο蠛秃瘮?shù)式編程。如果你覺得這有點奇怪,那你不是一個人,因為所有人都這么覺得:函數(shù)式編程的目標是完全消除副作用,而面向?qū)ο缶幊淌前迅弊饔帽A粼趯ο髢?nèi)部。
盡管如此,很多開發(fā)人員認為 Scala
是一種幫助他們從面向?qū)ο缶幊踢^渡到函數(shù)式編程的語言。或許在未來幾年里,它們會更容易全面發(fā)揮作用。
(推薦教程:Scala教程)
4.Python
Python
鼓勵函數(shù)式編程。一個事實就能看到這一點:每個函數(shù)在默認情況下至少有一個輸入self
。這很像Python
的禪:顯式比隱式好!
(推薦教程:python教程)
5.Clojure
據(jù)它的創(chuàng)建者說,Clojure
大約有 80% 是函數(shù)式編程。默認情況下,所有值都是不可變的,就像在函數(shù)式編程中需要它們一樣。但是,我們可以通過在這些不可變的值周圍使用可變值包裝器來解決這個問題。當你打開這樣一個包裝,你得到的東西又是不變的。
(推薦教程:Clojure教程)
6.Haskell
這是為數(shù)不多的純函數(shù)式和靜態(tài)類型的語言之一。雖然在開發(fā)過程中這看起來像是一個時間消耗器,但在調(diào)試程序時,Haskell
會付出巨大的代價。它不像其他語言那么容易學,但絕對值得投資!
(推薦教程:Real World Haskell 中文版)
大數(shù)據(jù)時代帶來了函數(shù)式編程
與面向?qū)ο缶幊滔啾龋瘮?shù)式編程仍然是一個新生兒。但是如果在 Python
和其他語言中包含函數(shù)式編程原理,具有不一樣的意義,那么函數(shù)式編程就有可能獲得關(guān)注。
函數(shù)式編程對于大型數(shù)據(jù)庫、并行編程和機器學習非常有用。在過去的十年里,所有這些都在蓬勃發(fā)展。
雖然面向?qū)ο蟠a有著不可估量的優(yōu)點,但函數(shù)代碼的優(yōu)點卻不容忽視。只需要學習一些基本原理,就足以讓我們成為一名開發(fā)人員,并為未來做好準備。
以上就是關(guān)于函數(shù)式編程的相關(guān)介紹了,希望對大家有所幫助。