來源:王垠
好些人來信問我,要成為一個好的程序員,數(shù)學基礎(chǔ)要達到什么樣的程度?十八年前,當我成為大學計算機系新生的時候,也為同樣的問題所困擾。面對學數(shù)學,物理等學科的同學,我感到自卑。經(jīng)常有人說那些專業(yè)的知識更加精華一些,難度更高一些,那些專業(yè)的人畢業(yè)之后如果做編程工作,水平其實比計算機系畢業(yè)的還要高。直到幾年前深入研究程序語言之后,對這個問題我才得到了答案和解脫。由于好多編程新手遇到同樣的困擾,所以我想在這里把這個問題詳細的闡述一下。
數(shù)學并不是計算機科學的基礎(chǔ)
很多人都盲目的認為,計算機科學是數(shù)學的一個分支,數(shù)學是計算機科學的基礎(chǔ),數(shù)學是更加博大精深的科學。這些人以為只要學會了數(shù)學,編程的事情全都不在話下,然而事實卻并非如此。
事實其實是這樣的:
計算機科學其實根本不是數(shù)學,它只不過借用了非常少,非常基礎(chǔ)的數(shù)學,比高中數(shù)學還要容易一點。所謂“高等數(shù)學”,在計算機科學里面基本用不上。
計算機是比數(shù)學更加基礎(chǔ)的工具,就像紙和筆一樣。計算機可以用來解決數(shù)學的問題,也可以用來解決不是數(shù)學的問題,比如工程的問題,藝術(shù)的問題,經(jīng)濟的問題,社會的問題等等。
計算機科學是完全獨立的學科。學習了數(shù)學和物理,并不能代替對計算機科學的學習。你必須針對計算機科學進行學習,才有可能成為好的程序員。
數(shù)學家所用的語言,比起常見的程序語言(比如C++,Java)來說,其實是非常落后而糟糕的設(shè)計。所謂“數(shù)學的美感”,其實大部分是夜郎自大。
99%的數(shù)學家都寫不出像樣的代碼。
數(shù)學是異常糟糕的語言
這并不是危言聳聽。如果你深入研究過程序語言的理論,就會發(fā)現(xiàn)其實數(shù)學家們使用的那些符號,只不過是一種非常糟糕的程序語言。數(shù)學的理論很多是有用的,然而數(shù)學家門用于描述這些理論所用的語言,卻是紛繁復雜,缺乏一致性,可組合性(composability),簡單性,可用性。這也就是為什么大部分人看到數(shù)學就頭痛。這不是他們不夠聰明,而是數(shù)學語言的“設(shè)計”有問題。人們學習數(shù)學的時候,其實只有少部分時間在思考它的精髓,而大部分時間是在折騰它的語法。
舉一個非常簡單的例子。如果你說cos2θ表示(cos θ)2,那么理所當然,cos-1θ就應該表示1/(cos θ)了?可它偏偏不是!別被數(shù)學老師們的教條和借口欺騙啦,他們總是告訴你:“你應該記住這些!” 可是你想過嗎:“憑什么?” cos2θ表示(cos θ)2,而cos-1θ,明明是一模一樣的形式,表示的卻是arccos θ。一個是求冪,一個是調(diào)用反函數(shù),風馬不及,卻寫成一個樣子。這樣的語言設(shè)計混淆不堪,卻喜歡以“約定俗成”作為借口。
如果你再多看一些數(shù)學書,就會發(fā)現(xiàn)這只是數(shù)學語言幾百年累積下來的糟粕的冰山一角。數(shù)學書里盡是各種上標下標,帶括號的上標下標,x,y,z,a,b,c,f,g,h,各種扭來扭去的希臘字母,希伯來字母…… 斜體,黑體,花體,雙影體,……用不同的字體來表示不同的“類型”。很多符號的含義,在不同的子領(lǐng)域里面都不一樣。有些人上一門數(shù)學課,到最后還沒明白那些符號是什么意思。
直到今天,數(shù)學家們寫書仍然非常不嚴謹。他們常犯的一個錯誤是把x2這樣的東西叫做“函數(shù)”(function)。其實x2根本不是一個函數(shù),它只是一個表達式。你必須同時指明“x是參數(shù)”,加上x2,才會成為一個函數(shù)。所以正確的函數(shù)寫法其實看起來像這樣:f(x) = x2。或者如果你不想給它一個名字,可以借用lambda calculus的寫法,寫成:λx.x2。
可是數(shù)學家們灰常的喜歡“約定俗成”。他們定了一些不成文的規(guī)矩是這樣:凡是叫“x”的,都是函數(shù)的參數(shù),凡是叫“y”的,都可能是一個函數(shù)…… 所以你寫x2就可以表示λx.x2,而不需要顯式的寫出“λx”。殊不知這些約定俗成,看起來貌似可以讓你少寫幾個字,卻造成了許許多多的混淆和麻煩。比如,你在Mathematica里面可以對 x2+y 求關(guān)于x的導數(shù),而且會得到 y'(x) + 2x 這樣蹊蹺的結(jié)果,因為它認為y可能是一個函數(shù)。更奇怪的是,如果你在后面多加一個a,也就是對x2+y+a求導,你會得到 2x!那么 y'(x) 到哪里去了?莫名其妙……
相對而言,程序語言就嚴謹很多,所有的程序語言都要求你必須指出函數(shù)的參數(shù)叫什么名字。像x2這樣的東西,在程序語言里面不是一個函數(shù)(function),而只是一個表達式(expression)。即使 JavaScript 這樣毛病眾多的語言都是這樣。比如,你必須寫:
function (x) { return x * x }
那個括號里的(x),顯式的聲明了變量的名字,避免了可能出現(xiàn)的混淆。我不是第一個指出這些問題的人。其實現(xiàn)代邏輯學的鼻祖Gottlob Frege在一百多年以前就在他的論文“Function and Concept”里批評了數(shù)學家們的這種做法。可是數(shù)學界的表達方式直到今天還是一樣的混亂。
很多人學習微積分都覺得困難,其實問題不在他們,而在于萊布尼茲(Leibniz)。萊布尼茲設(shè)計來描述微積分的語言(∫,dx, dy, …),從現(xiàn)代語言設(shè)計的角度來看,其實非常之糟糕,可以說是一塌糊涂。我不能怪萊布尼茲,他畢竟是幾百年前的人了,他不知道我們現(xiàn)在知道的很多東西。然而古人的設(shè)計,現(xiàn)在還不考慮改進,反而當成教條灌輸給學生,那就是不思進取了。
數(shù)學的語言不像程序語言,它的歷史太久,沒有經(jīng)過系統(tǒng)的,考慮周全的,統(tǒng)一的設(shè)計。各種數(shù)學符號的出現(xiàn),往往是歷史上某個數(shù)學家有天在黑板上隨手畫出一些古怪的符號,說這代表什么,那代表什么,…… 然后就定下來了。很多數(shù)學家只關(guān)心自己那塊狹窄的子領(lǐng)域,為自己的理論隨便設(shè)計出一套符號,完全不管這些是否跟其它子領(lǐng)域的符號相沖突。這就是為什么不同的數(shù)學子領(lǐng)域里寫出同樣的符號,卻可以表示完全不同的涵義。在這種意義上,數(shù)學的語言跟Perl(一種非常糟糕的程序語言)有些類似。Perl把各種人需要的各種功能,不加選擇地加進了語言里面,造成語言繁復不堪,甚至連Perl的創(chuàng)造者自己都不能理解它所有的功能。
數(shù)學的證明,使用的其實也是極其不嚴格的語言——古怪的符號,加上含糊不清,容易誤解的人類語言。如果你知道什么是Curry-Howard Correspondence就會明白,其實每一個數(shù)學證明都不過是一段代碼。同樣的定理,可以有許多不同版本的證明(代碼)。這些證明有的簡短優(yōu)雅,有的卻冗長繁復,像面條一樣繞來繞去,沒法看懂。你經(jīng)常在數(shù)學證明里面看到“未定義的變量”,證明的邏輯也包含著各種隱含知識,思維跳躍,非常難以理解。很多數(shù)學證明,從程序的觀點來看,連編譯都不會通過,就別提運行了。
數(shù)學家們往往不在乎證明的優(yōu)雅性。他們認為只要能證明出定理,你管我的證明簡不簡單,容不容易看懂呢。你越是看不懂,就越是覺得我高深莫測,越是感覺你自己笨!這種思潮到了編程的時候就顯出弊端了。數(shù)學家寫代碼,往往忽視代碼的優(yōu)雅性,簡單性,模塊化,可讀性,性能,數(shù)據(jù)結(jié)構(gòu)等重要因素,認為代碼只要能算出結(jié)果就行。他們把代碼當成跟證明一樣,一次性的東西,所以他們的代碼往往不能滿足實際工程的嚴格要求。
數(shù)學里最在乎語言設(shè)計的分支,莫過于邏輯學了。很多人(包括很多程序語言專家)都盲目的崇拜邏輯學家,盲目的相信數(shù)理邏輯是優(yōu)雅美好的語言。在程序語言界,數(shù)理邏輯已經(jīng)成為一種災害,明明很容易就能解釋清楚的語義,非得寫成一堆稀奇古怪,含義混淆的邏輯公式。殊不知其實數(shù)理邏輯也是有很大的歷史遺留問題和誤區(qū)的。研究邏輯學的人經(jīng)常遇到各種“不可判定”(undecidable)問題和所謂“悖論”(paradox),研究幾十年也沒搞清楚,而其實那些問題都是他們自己造出來的。你只需要把語言改一下,去掉一些不必要的功能,問題就沒了。但邏輯學家們總喜歡跟你說,那是某天才老祖宗想出來的,多么多么的了不起啊,不能改!
用一階邏輯(first-order logic)這樣的東西,你可以寫出一些毫無意義的語句。邏輯老師們會告訴你,記住啦,這些是沒有意義的,如果寫出來這些東西,是你的問題!他們沒有意識到,如果一個人可以用一個語言寫出毫無意義的東西,那么這問題在于這個語言,而不在于這個人。一階邏輯號稱可以“表達所有數(shù)學”,結(jié)果事實卻是,沒有幾個數(shù)學家真的可以用它表達很有用的知識。到后來,稍微明智一點的邏輯學家們開始研究這些老古董語言到底出了什么毛病,于是他們創(chuàng)造了Model Theory這樣的理論。寫出一些長篇大部頭,用于“驗證”這些邏輯語言的合理性。這些問題在我看來都是顯而易見的,因為很多邏輯的語言根本就不是很好很有用的東西。去研究它們“為什么有毛病”,其實是白費力氣。自己另外設(shè)計一個更好語言就完事了。
在我看來,除了現(xiàn)代邏輯學的鼻祖Gottlob Frege理解了邏輯的精髓,其它邏輯學家基本都是照本宣科,一知半解。他們喜歡把簡單的問題搞復雜,制造一些新名詞,說得玄乎其玄靈丹妙藥似的。如果你想了解邏輯學的精華,建議你看看Frege的文集。看了之后你也許會發(fā)現(xiàn),F(xiàn)rege思想的精華,其實已經(jīng)融入在幾乎所有的程序語言里了。
編程是一門藝術(shù)
從上面你也許已經(jīng)明白了,普通程序員使用的編程語言,就算是C++這樣毛病眾多的語言,其實也已經(jīng)比數(shù)學家使用的語言好很多。用數(shù)學的語言可以寫出含糊復雜的證明,在期刊或者學術(shù)會議上蒙混過關(guān),用程序語言寫出來的代碼卻無法混過計算機這道嚴格的關(guān)卡。因為計算機不是人,它不會迷迷糊糊的點點頭讓你混過去,或者因為你是大師就不懂裝懂。代碼是需要經(jīng)過現(xiàn)實的檢驗的。如果你的代碼有問題,它遲早會導致出問題。
計算機科學并不是數(shù)學的一個分支,它在很大程度上是優(yōu)于數(shù)學,高于數(shù)學的。有些數(shù)學的基本理論可以被計算機科學所用,然而計算機科學并不是數(shù)學的一部分。數(shù)學在語言方面帶有太多的歷史遺留糟粕,它其實是泥菩薩過河,自身難保,它根本解決不了編程中遇到的實際問題。
編程真的是一門藝術(shù),因為它符合藝術(shù)的各種特征。藝術(shù)可以利用科學提供的工具,然而它卻不是科學的一部分,它的地位也并不低于科學。和所有的藝術(shù)一樣,編程能解決科學沒法解決的問題,滿足人們新的需求,開拓新的世界。所以親愛的程序員們,別再為自己不懂很多數(shù)學而煩惱了。數(shù)學并不能幫助你寫出好的程序,然而能寫出好程序的人,卻能更好的理解數(shù)學。我建議你們先學編程,再去看數(shù)學。