Python函數語言程式設計系列002:水管模型和compose

三次方根發表於2021-10-01

水管模型

這一章,我們要回到一個問題,到底函式式和過程式的程式設計思路到底在哪裡?我們這裡提供一個形象的比喻。

過程式--屋子物件

過程式思維裡,每一個可變變數、函式/過程指稱的符號,類似告訴你一個屋子的名字。對一個靜態語言來說,我們可能還需要屋子裡只能放什麼東西。然後,我們每一次呼叫一次函式/過程,就是將對對應屋子裡的函式取出來,以及其他屋子裡的物件取出來,按照函式的方式重新整理,把結果放到原先的屋子或者新的屋子裡。(注意這個描述和圖靈機其實是類似的。)

但是,這個屋子可能會更復雜,我們可能有時候會參考別的屋子裡的情況,甚至另一棟房子裡的屋子(其他模組、第三方外掛),或者甚至是天氣和社會新聞(環境變數、硬體)來盤算每一次整理物件的邏輯(即函式)。這個是「屋子-物件」模型中,最讓人困惑的事。如果你天氣不好,或者別的屋子的情況有問題,你的整理物件的規則可能就有很大的問題,而且溯因是困難的。這個我們也在001中介紹過這個問題。

函式式--水管和資料流

在函數語言程式設計中,我們的模型是編造一些列的水管,水管就是函式式中的函式。我們的目標就是事先將各種函式水管給架設好管道系統。然後將水(資料/不可變引數)倒入進去,等待水管的另一頭流出結果就好了。就像下面的圖顯示的那樣:

stateDiagram-v2

    [*] --> function
    function --> [*]

compose

我們到目前為止,可以想到的最簡單的就是將水管相連。比如,在做文字處理的時候,我們很有可能會有以下的操作。這個就是典型的水管拼接的過程,我們只需要維護分詞變小寫刪除stopword詞幹化這幾個函式即可。

stateDiagram-v2

    [*] --> 分詞
    分詞 --> 變小寫
    變小寫 --> 刪除stopwords
    刪除stopwords --> 詞幹化
    詞幹化 --> [*]

當然,我們可以一步步把水倒到水管裡,取出來再倒到另一個水管。那何不我們就直接事先幫水管串起來。這個操作也被稱為compose(用符號\(\circ\)表示),數學表述如下:

$$(f \circ g) x = f(g(x))$$

我們給出一個簡單的Python實現:

from functools import reduce

def compose(*args):
    """數學中的compose

    >>> from fppy.base import compose
    >>> compose(lambda x: x+1, lambda x: x**2)(1)
    >>> 4

    """
    return reduce(lambda f, g: lambda x: f(g(x)), args, lambda x: x)

比如我們就可以把下面的f1f2f3給串起來了:

>>> f1 = lambda x: x + 1
>>> f2 = lambda y: y * 2
>>> f3 = lambda z: z / 3

>>> compose(f3, f2, f1)(1)
1.3333333333333333

>>> h(g(f(1)))
1.3333333333333333

不過有時候,compose的順序會讓人困惑,我個人喜歡下面and_then的表述:

def and_then(*args):
    return reduce(lambda f, g: lambda x: g(f(x)), args)

對我個人而言這樣子會更明確:

>>> and_then(f, g, h)(1)
1.3333333333333333

不過在具體實現裡,我們其實用到了作為引數的函式的概念,這個也是函數語言程式設計中「函式是一等公民」的表現,具體各種水管模式(聽起來我們是長鬍子的法國水管工)我們將在下面的文章中一一展現。

相關文章