昨天我們硬是把 Python 的一些傳統寫法,包裝成為函數式的模式來呼叫。但我們距離真正的函數式,仍有段路程。撇除高深的 Functor、Monad 之類的概念不談,我們所用的資料結構,本身是一種容易被改變的資料結構,而 tuple 雖然不可變,但稍缺彈性。我們有沒有一個介於其中的資料結構可供操作:可以添加值進去,但不會改變原本的那個物件參考?
Pyrsistent
通常我開的 Python 專案都會有這個,我也建議各位使用。這是一個 Clojure (JVM 上的 Lisp) 的三大資料結構 (List、Map、Set) 的 Python 實作。Pyrsistent 提供一整組實現不可變及可擴展兩個目標的資料結構。先用 pip
或 pipenv
安裝它吧:
pip install pyrsistent
或
pipenv install pyrsistent
安裝完成後,就能在你的 Python repl 或專案中使用:
from pyrsistent import v, pvector
它的 List 不叫 PList,叫 PVector,主要是因為它使用 Clojure 的稱呼。我們通常會用到的,就是 v()
,它可以這樣用:
v1 = v()
v2 = v1.append('a')
v3 = v2.append('b')
v4 = v3.set(1, 'c')
v5 = pvector(x for x in v4) # 使用 List comprehension 時,要用 pvector
v1 # pvector([])
v2 # pvector(['a'])
v3 # pvector(['a', 'b'])
v4 # pvector(['a', 'c'])
v5 # pvector(['a', 'c'])
這什麼意思?意思是,當今天我建立了一個 list (PVector) 之後,若是對它增加、修改,或刪除內容時,它會回傳一個新的物件參考,而非原有的那個。這麼一來,當我把這個 list 傳到別的函式裡,裡頭不論是操作 append
、set
等任何改動時,都不會影響我原本對這 list 內容的認定。
而除了 list 的操作外,dict 與 set 皆有對應的處理,建議各位可以上 Pyrsistent 的 GitHub 了解更多的細節。
有了這樣的資料結構,我們可以做什麼事呢?
以下是不良示範
def build_list(n, func, init):
if n == 0:
return init
else:
return build_list(n - 1, func, init.append(func(n)))
reversed_fib_list = build_list(10, fib, v())
這個程式改寫昨天的 build_list
,加入 init
,然後在函式回傳的機制上,使用了尾遞迴的方式來處理。然而,這不是個好做法,我只是用它來解釋用 Python 的語法做出如同函數式語言一般的尾遞迴。
但這不好的地方在哪?因為 Python 並沒有針對尾遞迴進行最佳化,在函數式語言中,當語言的 runtime 或 compiler 發現有尾遞迴時,會把它拆開,變成一個類似 for 迴圈的結構,以優化執行效率,因此不太建議在 Python,甚至是 Java 裡頭,使用這種尾遞迴,雖然它看起來很優雅。(當然審慎評估後,有時我還是會用的)
函數式的開發,除了弄了個具有不變性的資料結構外,有沒有人想過,每次函數式語言在開發時,透過一些遞迴的方式在寫,這不會很慢嗎?
既然函數式的開發具有引用透明度,那麼能肯定的是,傳入固定的參數,回傳結果都會是一樣的。既然如此,何不把這些過程給記憶 (memorize) 起來呢?
memorize
另一個我常搭配著使用的,就是這 memorize 套件。先用 pip 或 pipenv 把它裝起來:
pip install memorize
裝完後,我們把這個 memorize()
加到 fib
上頭,再執行看看會不會比較快:
from memorize import memorize
f0 = 1
f1 = 1
@memorize()
def fib(n):
# 以下省略
可以肯定的是,加上這個之後,你就可以比較有耐心求取 fib(20) 以上的值了。
memorize
還有其他的設定,例如資料保留多久、快取空間多大等等,但最重要的是,這個機制只能用在當你確定這個函式傳固定參數進去後,一定會回傳同樣的結果,才能使用它。
結語
這兩天的內容,是函數式開發的基本,非常的入門。函數式不是 Python 主要的開發典範,但透過一些語言特性與函式庫的機制,Python 開發者也能為程式注入函數式的優雅與清晰。
關於函數式的開發,可以關注臉書上的 Functional Thursday 社群,這是個在台北的函數式研究社群,在上面可以學到許多道地的 FP 知識。
明後天,我們會來思考 Python 的物件導向。