續延,有什麼難的……

學 C 語言,攔路虎是指針。學 Haskell,攔路虎是單子(Monad)。學 Scheme,攔路虎是什麼呢?初學者以爲是層巒疊嶂的括號,其實是續延(Continuation)。segmentfault

我曾用過 C 的指針(數據指針 + 函數指針)模擬過單子 [1],其實已經觸及了續延。還記得那個 bar 函數吧,函數

Maybe bar(Maybe a, Maybe (*contuation)(void *thing))
{
        return a.thing ? contuation(a.thing) : (Maybe){.thing = Nothing};
}

contuation 指針指向一個函數。這個函數用於處理 bar 不可以肯定或不知道如何處理的事情,這種事情對於 bar 而言,可稱爲一切來自將來的計算,此時,這個函數能夠稱爲 bar 的續延。也就是說,bar 函數完成它能完成的計算以後,會將計算結果傳遞給 contuation。固然,這在 C 語言裏是無法作,可是能夠這樣認爲。指針

要作到「這樣認爲」,須要作一點腦力體操。code

看下面的表達式,它基於 bar 函數輸出結果的賦值表達式,可是我故意不給 bar 函數提供第 2 個參數,而是用一個 [] 來表示它:內存

Maybe b = bar(foo(Nothing), []);

假若我說,這個表達式等效於 1 個單參數函數,你信麼?get

假若你不信……我想不出來爲啥你會不信。須要 1 個參數才能求值的計算過程,不就是一個單參數的函數麼?編譯器

不信的話,那麼就將 bar(foo(Nothing), []) 打開,即:io

return foo(Nothing).thing ? [](foo(Nothing).thing) : (Maybe){.thing = Nothing};

看上去,是一些表達式包圍着 []。從如今起,要認爲 [] 表示一個洞,洞外的表達式掉入了這個洞,然後洞裏發生了一些事,最後從這個洞裏又掉出來一個東西。要達到這種認識,可能須要懂一點王陽明創立的心學,即「心外無物,心外無理」。你以爲是這些表達式包圍了洞,可是沒了這個洞,這些表達式是沒意義的,等同於不存在。編譯

假若仍是沒開竅,那就拿一隻杯子。將杯子的內部視爲「外」,將杯子的外部視爲「內」,那麼這個杯子就包含了整個宇宙。假若這個杯子不存在,那麼宇宙也就沒了意義,等同於不存在。接下來,你拿錘子敲掉這個杯子的底,你能夠說這個宇宙從杯子的一個口掉了進去,又從另外一個口掉了出來。test

袖裏乾坤大,壺中日月長。不吹,也不黑。

通過上述腦力體操以後,我宣稱,bar 函數的返回值掉進了這個洞裏,而後從這個洞裏出來的東西做爲值賦給了 b,應該很好理解。儘管它與咱們的生活經驗不太相符。

記住,帶洞的表達式就是續延。能夠認爲,這個洞外面的東西會掉進這個洞裏。不妨將這個洞稱爲「黑洞」。它對掉進來的東西做何處理,要看這個黑洞是什麼樣的黑洞了。

對於

bar(foo(Nothing), []);

下面這個函數是一個對掉進來的整型數乘以 10 的黑洞:

Maybe test(void *thing)
{
        (*(int *)thing) *= 10;
        return (Maybe){.thing = thing};
}

從這個黑洞裏掉出來的值,最終被賦給 b,即:

Maybe b = bar(foo(Nothing), test);

再強調一下,在 C 語言裏不是這樣進行運算的,只是能夠這樣認爲,並且只要你願意,能夠爲 C 語言寫一個這樣的編譯器。

如今,將 b 的賦值表達式的右半部分去掉,讓它變成:

Maybe b = [];

這樣,一個賦值表達式也能構成一個續延,由於它裏面出現了黑洞。掉進黑洞裏的 b(內存地址),會被黑洞與一個值(數據)綁定起來,而後它們從這個黑洞裏掉出來。

基於續延裏的黑洞,可以將多個續延串接起來,只須要將黑洞串接起來便可。例如,在 C 語言裏,我要用 bar 組合一組相同類型的函數,能夠這樣作:

Maybe x = bar(bar(bar(foo(a), f), g), h);
注: 假設 a 已知, foo(a) 構造一個 Maybe 變量。

這個表達式有三重續延,它們像俄羅斯套娃同樣。在 C 語言裏,只能經過「看做是」的辦法,認爲整個表達式首先掉進了最內層的黑洞 f。從 f 裏掉出來東西又掉進了第二層黑洞 g。從 g 裏掉出來的東西又調入了第三層也就是最外層黑洞 h。從h 裏掉出來的東西做爲值賦給變量 x

將一個續延做爲參數傳給另外一個續延,這就是所謂的續延傳遞,用這種辦法能夠造成一條有序的控制流(幾個黑洞銜接起來)。用這種辦法寫程序,就是所謂的續延傳遞風格(Continuation-Passing Style,CPS)。

以上所述的東西,對於 C 的世界而言,是想象,可是對於 Scheme 的世界而言,就是客觀存在。在 Scheme 的世界裏,函數與續延,都是一等公民。函數能夠做爲參數傳遞給函數,續延能夠做爲參數傳遞給續延,函數能夠做爲參數傳遞給續延,續延能夠做爲參數傳遞給函數。

在 Scheme 裏,假設也有一個 bar 函數,那麼它將 fgh 這三個黑洞串接起來並將最後一個黑洞掉出來的結果賦值給一個變量,可用如下手法:

(define x
  (bar (foo a)
       (lambda (b)
         (bar (f b)
              (lambda (c)
                (bar (g c)
                     (lambda (d)
                       (foo d))))))))

值得注意的是,bar 函數是單子的核心部分,而 bar 函數能夠視爲一個續延,它的黑洞就是 contuation 指向的函數。如此說來,續延要比單子更基層。不過,單子也不是蓋的,它也有辦法規避 bar 這樣的函數,並且使得本身依然是單子,而且它還能演繹出 bar 這樣的函數。那麼,將續延和單子當作一回事,如何?

在實踐中,續延一般用來表達「等待」。

有詩云,有約不來過夜半,閒敲棋子落燈花。敲着棋子看燈花的人是一個續延,他掉進了爽約的人構成的黑洞裏,從而失去了時間。

最後,來作一道哲學題:

先生(指王陽明)遊南鎮,一友指巖中花樹問曰:「天下無意外之物,如此花樹在深山中自開自落,於我心亦何相關?」先生曰:「你未看此花時,此花與汝心同歸於寂;你來看此花時,此花顏色一時明白起來,便知此花不在你的心外。」

王陽明這一觀點的錯誤是( )

A.把人對花的感受與花的存在等同起來
B.把人對花的感受與花的存在對立起來
C.主張人對花的感受是主觀與客觀的統一
D.確定人對花的感受的能動性

這個題目,上過大學,學過馬哲的人差很少都作過。王陽明龍場悟道,創立心學。他的成就,即便放到現代,能超越的人幾乎不存在。結果,堂堂一代宗師,就被這麼一個題目給毀了。

王陽明的觀點沒有錯誤,反正馬克思沒說他錯了。說他這個觀點錯了的人,是學過馬哲但沒學過心學的人。大學不教心學,只教馬哲,因此呢,全中國的大學生都會以爲王陽明太惟心,並且仍是主觀惟心……惟心就是封建迷信……至少我上學的時候,作了這個題目以後就是這麼認爲的。

觀點自己沒有對錯。只是觀點與受體可能會存在類型不匹配問題,從而觸發了受體的異常機制。

有點跑題了。我要說的是續延。對於王陽明看花這個例子,能夠理解爲,花是一個續延,王陽明是它的黑洞,從這個黑洞裏掉出來的結果是「王陽明心中的花」。王陽明的友人也是這朵花的黑洞,從這個黑洞裏掉出來的結果是「王陽明友人心中的花」。那朵花自己沒有變,只是掉入了不一樣的黑洞裏時,輸出的結果有所不一樣。

假若全世界的人或者任何蟲魚禽獸都沒看過那朵花,它會怎樣?我不是很清楚。不過,假若我是那朵花,我會嘗試本身「看」本身——將本身做爲續延,再將這個續延做爲黑洞,結果獲得的是「自我」。


[1] 單子,想弄不懂都很難

相關文章
相關標籤/搜索