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







你可能感興趣的文章

[Note] React - Hooks: useMemo

[Note] React - Hooks: useMemo

Understanding Global Conflic and Cooperration

Understanding Global Conflic and Cooperration

redis 套件的 Property 'on' does not exist on type 'RedisClientType'

redis 套件的 Property 'on' does not exist on type 'RedisClientType'






留言討論