day_03: 我好像有點懂函數式了...


昨天我們硬是把 Python 的一些傳統寫法,包裝成為函數式的模式來呼叫。但我們距離真正的函數式,仍有段路程。撇除高深的 Functor、Monad 之類的概念不談,我們所用的資料結構,本身是一種容易被改變的資料結構,而 tuple 雖然不可變,但稍缺彈性。我們有沒有一個介於其中的資料結構可供操作:可以添加值進去,但不會改變原本的那個物件參考?

Pyrsistent

通常我開的 Python 專案都會有這個,我也建議各位使用。這是一個 Clojure (JVM 上的 Lisp) 的三大資料結構 (List、Map、Set) 的 Python 實作。Pyrsistent 提供一整組實現不可變及可擴展兩個目標的資料結構。先用 pippipenv 安裝它吧:

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 傳到別的函式裡,裡頭不論是操作 appendset 等任何改動時,都不會影響我原本對這 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 的物件導向。

#Python #Functional Programming #Pyrsistent #memorize







你可能感興趣的文章

網路爬蟲 --- 同業公會名單下載

網路爬蟲 --- 同業公會名單下載

[ JavaScript 02 ] 變數

[ JavaScript 02 ] 變數

How to create a two dimensional array in JavaScript?

How to create a two dimensional array in JavaScript?






留言討論