單子,想弄不懂都很難

garfileo發表於2019-05-11

C 語言裡沒有現代程式設計師熱衷於討論的那些東西。

不過,那些東西不是原本就沒有麼?

下面我嘗試用 C 語言來寫一個單子(Monad)。

看下面這段程式碼:

typedef struct {
        void *thing;
} Maybe;

在 C 語言裡,這是個結構體,而且是一個似乎很無聊的結構體。這種結構體能用來做什麼呢?

可以作為函式的返回值型別。例如:

Maybe foo(void *thing)
{
        return (Maybe){.thing = thing};
}

假設 foo 像下面這樣呼叫:

Maybe a = foo(thing); /* thing 指向前面出現的某個變數 */

那麼,當 a.thingNULL 時,表示 foo 函式執行失敗,否則表示執行成功。

那麼 foo 函式的作用就將一種型別包裝成 Maybe 型別。

NULL 可能會嚇著一些對 C 語言原本就沒怎麼有好感的人,可以給它換個名字:

#define Nothing NULL

現在,可以認為當 a.thingNothing 時,表示 foo 函式執行失敗,否則表示執行成功。

接下來,為 Maybe 型別定義這樣的函式:

Maybe aha(void * (*f)(void *), Maybe a)
{
        return foo(f(a.thing));
}

這個函式可以將一個函式 f 作用於 a.thing。假設這個 f 為:

void *textize(void *thing)
{
        int *x = thing;
        char *text = malloc(32 * sizeof(char));
        sprintf(text, "%d", *x);
        return text;
}

有了像 textize 這樣的函式,就可以用 aha 函式了,如下:

int a = 2;
Maybe x = aha(textize, foo(&a));

結果得到的 x.thing 會指向一個字串 "2"

一個型別再加上一個針對這種型別的 aha 這樣的函式,叫函子

現在,再為 Maybe 型別構造一個函式:

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

現在,可以宣佈有了一個 Maybe 單子。

這個 bar 函式有什麼用呢,它能夠按照順序安全地組合一組形狀相同的函式,從而起到一個函式的效果。例如,對於下面的三個函式:

Maybe test_a(void *thing)
{
        (*(int *)thing) *= 10;
}

Maybe test_b(void *thing)
{
        (*(int *)thing) *= 100;
}

Maybe test_c(void *thing)
{
        (*(int *)thing) += 1000;
}

使用 bar 可以把它們按照順序裝配起來:

int a = 2;
Maybe x = bar(bar(bar(foo(&a), test_a), test_b), test_c);

結果 x.thing 依然是指向變數 a 的指標,經過一組函式的處理,a 的值變成了 3000。

與上述程式碼等價的程式碼,也是 C 程式設計師慣用的程式碼如下:

int a = 2;
Maybe x = foo(&a);
if (x.thing) {
        x = test_a(x.thing);
        if (x.thing) {
                x = test_b(x.thing);
                if (x.thing) {
                        x = test_c(x.thing);
                }
        }
}

一個單子,由一種型別以及針對這種型別的類似 foobar 這樣的函式構成。

上面我用的 fooaha 以及 bar 這些名字,有一些惡意的調侃。值得注意的是,aha 函式實際上可以基於 foobar 來定義。例如:

Maybe aha(void * (*f)(void *), Maybe a)
{
        return bar(foo(f(a.thing)), foo);
}

雖然稍微有點繞圈子,但是 aha 的確是基於 foobar 定義了出來。這說明了什麼呢?

單子的層次高於函子。

倘若你能理解上述程式碼所體現出來的形式,那麼上面出現的東西是叫物件、態射、函子、自函子、自然變換、自函子範疇、么半群、單子,還是別的什麼東西,很重要嗎?我以為這些東西只是對研究數學的人很重要,而對於程式設計的人來說……只要你寫的程式碼足夠簡約,足夠具備複用性,那麼就一點都不重要。

倘若覺得很重要,那麼好吧,就掰扯一下,以問答的形式。

Q:什麼是範疇?

A:範疇由物件和態射構成。例如,C 語言裡的資料型別與函式。

Q:什麼是函子?

A:函子可以將一種物件變成另一種物件,將一種態射變成另一種態射。例如,上面的 Maybe,將 void * 變成了 Maybe 型別;上面的 ahatextize 這種 void * -> void * 的函式變成了 Maybe -> Maybe 的函式。

Q:什麼是自函子?

A:將一個範疇裡的物件和態射變成這個範疇裡的物件和態射的函子就是自函子。作家、畫家、歌唱家、演員、舞者、政客、運動員……從事各種職業的人,都是自函子。什麼職業都不從事的人也是自函子,他們就是 foo 函式。也有一些不是自函子的人,多數在精神病院裡,還有一些在外面跳大神。

Q:什麼是自函子範疇?

A:一個自函子構成的範疇。這個範疇裡就一個東西,這個自函子。這個範疇裡面的態射,叫自然變換。foobar 都是自然變換。

Q:什麼是自函子範疇上的么半群?

A:將自函子範疇裡的所有態射視為集合,那個自函子就消失了。也就是說,自函子轉化成了一個態射集合,這個集合叫 Hom-集。想一想自個,當你愛上一個人或什麼東西的時候,你就消失了,我們管這個叫忘我。過去的人思慕成仙,要成仙,首先要忘我,然後去搞行為藝術。藝術是永恆的,對吧?這種行為藝術怎麼搞呢?需要先搞一個么元(也有很多人叫單位元)。這個么元啊,就是眼觀鼻、鼻觀心、意守丹田。有了么元,就可以驅動真氣,打通任督二脈……做到這兩步,就得到了么半群。

Q:玄學?

A:計算機裡執行的玄學。

相關文章