在電氣電子工程師學(xué)會(huì)(IEEE)發(fā)布的2016年編程語(yǔ)言排行榜中拼窥,R語(yǔ)言已經(jīng)沖到了第五名退敦,僅次于C折柠、Java芒炼、Python 和 C++。R語(yǔ)言的流行滨彻,大致是與數(shù)據(jù)科學(xué)的興起有關(guān)藕届。用R來(lái)分析數(shù)據(jù),成了一件很時(shí)髦的事情亭饵。
我在R語(yǔ)言及其前身S-Plus里面摸爬滾打了多年休偶,卻一點(diǎn)也不喜歡,因?yàn)镽語(yǔ)言有許多令我忍無(wú)可忍的腦殘?jiān)O(shè)計(jì)辜羊。在本文里踏兜,我舉兩個(gè)例子,展示冰山一角八秃。
例1:自作聰明的序列產(chǎn)生器
不少編程語(yǔ)言里碱妆,都有序列產(chǎn)生器,例如:
- Haskell 中
[3..7]
的計(jì)算結(jié)果是[3,4,5,6,7]
昔驱。而[7..3]
的結(jié)果是空表[]
疹尾。 - Matlab 與 Haskell 的行為類似,
3:7
的計(jì)算結(jié)果是3 4 5 6 7
舍悯。7:3
的結(jié)果是空矩陣Empty matrix: 1-by-0
航棱。 - Python 中采取 Dijkstra 慣例睡雇,整數(shù)區(qū)間不包含上界本身萌衬。于是
range(3,7)
的計(jì)算結(jié)果是[3,4,5,6]
。而range(7,3)
的結(jié)果與前兩種語(yǔ)言類似它抱,是空表[]
秕豫。
現(xiàn)在R登場(chǎng)了!
3:7
得到
3 4 5 6 7
這個(gè)還正常』煲疲現(xiàn)在祠墅,倒過(guò)來(lái)……
7:3
R竟然“聰明”地給出了一個(gè)倒序的向量!
7 6 5 4 3
在R里摸爬滾打了許多年歌径,我淺薄地認(rèn)為毁嗦,這種標(biāo)新立異其實(shí)是個(gè)大坑!
下面回铛,我們做一個(gè)程序狗准,列出 “從1、2茵肃、3腔长、4這四個(gè)數(shù)中,取出任意兩數(shù)” 的所有方案验残。為了描述清晰捞附,我先用 MATLAB 做出一個(gè)版本:
for i=1:4
for j=(i+1):4
fprintf('%d %d\n', i, j);
end
end
計(jì)算結(jié)果是:
1 2
1 3
1 4
2 3
2 4
3 4
下面我們將MATLAB版本直譯成R語(yǔ)言:
for (i in 1:4) {
for (j in (i+1):4)
print (c(i, j))
}
我們希望得到類似MATLAB版本的輸出,然而您没,R的計(jì)算結(jié)果令人發(fā)指:
[1] 1 2
[1] 1 3
[1] 1 4
[1] 2 3
[1] 2 4
[1] 3 4
[1] 4 5
[1] 4 4
這個(gè)結(jié)果讓人一時(shí)摸不著頭腦鸟召。在上面的結(jié)果中,前面六行沒(méi)有問(wèn)題氨鹏,但第七行也太離譜了药版!根據(jù)正常的邏輯,j
的最大值無(wú)論如何也就是4喻犁,為何會(huì)產(chǎn)生5呢槽片?這就是因?yàn)镽的序列產(chǎn)生器自作聰明!
這個(gè)問(wèn)題的重點(diǎn)是肢础,當(dāng)i=4
的時(shí)候还栓,j
的范圍到底是什么。包括MATLAB在內(nèi)的正常語(yǔ)言传轰,都會(huì)認(rèn)定此時(shí)j
的范圍是空集剩盒,從5到4產(chǎn)生一個(gè)序列,下界比上界大慨蛙,當(dāng)然是空集咯辽聊!但是R偏偏不這么想,它說(shuō)期贫,下界比上界大跟匆,那么我們產(chǎn)生一個(gè)逆向的序列吧:5,4
。于是通砍,就有了第七和第八行這兩對(duì)多余的答案玛臂。
這里烤蜕,一定有人說(shuō),博主SB迹冤,把 for (i in 1:4)
寫(xiě)成 for (i in 1:3)
避免下界比上界大的情況讽营,不就把這坑繞過(guò)去了嗎?我不想這么做泡徙,因?yàn)檎Z(yǔ)言是拿來(lái)用的橱鹏,不是拿來(lái)練習(xí)“繞坑”的!
例2:神奇的等號(hào)與箭頭
先做個(gè)變量 x 堪藐,賦值為0蚀瘸。然后,我們來(lái)玩一個(gè)簡(jiǎn)單的“比大小”的游戲庶橱,比較 x 和 -1 的大小贮勃。具體而言,做一個(gè)if
表達(dá)式苏章,完成以下邏輯:
- 如果 x 小于 -1寂嘉,取值為“躲”
- 否則取值為“策”
稍有常識(shí)的觀眾都知道 0 比 -1 大,所以當(dāng)x=0時(shí)枫绅,這個(gè)if
表達(dá)式的值應(yīng)該是“策”泉孩。那我們拭目以待:
x=0
if (x<-1) "躲" else "策"
在R里面執(zhí)行一下,結(jié)果是
"躲"
有一定經(jīng)驗(yàn)的讀者或許已經(jīng)發(fā)現(xiàn)問(wèn)題了并淋。我來(lái)班門(mén)弄斧寓搬,仔細(xì)拆解一下。在解釋這一行程序的時(shí)候县耽,R完成了一系列動(dòng)作:
- 把
<-
合在一起當(dāng)成賦值操作句喷。 - 把 x 賦值為 1。
- “賦值”這一操作給出一個(gè)值:1兔毙。
- 把數(shù)值1當(dāng)作“邏輯真”代入
if
表達(dá)式唾琼,條件滿足了:“躲”。
這里澎剥,一定有人說(shuō)锡溯,博主SB,專寫(xiě)有歧義的表達(dá)式哑姚,如果在 -1 之前加個(gè)空格:
if (x< -1) "躲" else "策"
不就把這坑繞過(guò)去了嗎祭饭?我還是不想這么做,因?yàn)檎Z(yǔ)言是拿來(lái)用的叙量,不是拿來(lái)練習(xí)“繞坑”的倡蝙!
關(guān)于這種設(shè)計(jì),我想說(shuō)幾句:
“賦值”這個(gè)操作宛乃,作為一種動(dòng)作悠咱,還是沒(méi)有值比較好。在沒(méi)有上下文的情況下征炼,給“動(dòng)作”定一個(gè)值析既,是一件反自然、反人類的事情谆奥。比如眼坏,“傘哥把我打了一頓” 這個(gè)動(dòng)作的值是什么,是“爽”還是“疼”呢酸些?在以上的
if
表達(dá)式中宰译,x<-1
就有個(gè)值。根據(jù)R的文檔魄懂,x<-1
的值沿侈,與賦值后的 x 一樣,在這里是 1市栗,這種行為與C語(yǔ)言的賦值語(yǔ)句類似缀拭。之后,這個(gè)值1填帽,雖然是一個(gè)數(shù)蛛淋,卻被if
拿去強(qiáng)行當(dāng)成“邏輯真”,這又與C語(yǔ)言的邏輯機(jī)制類似篡腌。R沒(méi)學(xué)到C語(yǔ)言的高效率褐荷,卻繼承了一大堆糟粕。如果R分不清我是要“賦值”嘹悼,還是要“比大小”叛甫,至少應(yīng)該給我個(gè)提示,讓我自行確認(rèn)吧杨伙。無(wú)論是什么語(yǔ)言合溺,在
if
表達(dá)式的條件里做“賦值”是很罕見(jiàn)的事情。在這個(gè)例子中缀台,x<-1
就是if
表達(dá)式的條件棠赛,但R解釋器對(duì)這種罕見(jiàn)的情況熟視無(wú)睹。相比之下膛腐,C語(yǔ)言雖有“賦值語(yǔ)句帶值”的坑睛约,而C的編譯器卻往往有不錯(cuò)的檢查機(jī)制。許多學(xué)過(guò)C語(yǔ)言的朋友哲身,都曾不小心寫(xiě)出過(guò)類似
if (x = 1) {
...
} else {
...
}
的結(jié)構(gòu)辩涝。在這種情況下,一個(gè)負(fù)責(zé)任的C編譯器會(huì)警告說(shuō)lvalue異常勘天≌看到這種警告捉邢,合格的C程序員都會(huì)再次確認(rèn),到底是要寫(xiě) x = 1
還是 x == 1
商膊。然而伏伐,C編譯器的警告功能,R解釋器卻沒(méi)學(xué)到:完全無(wú)視上文的 if
晕拆,不報(bào)錯(cuò)藐翎,不給出任何提示,一意孤行实幕。在這種情況下吝镣,再狡猾的程序員,也躲不過(guò)好 bug 袄ケ印末贾!
- 以上的例子中,程序員看到值不對(duì)整吆,可以迅速排錯(cuò)未舟。然而,錯(cuò)誤的程序能有時(shí)能產(chǎn)生正確的結(jié)果掂为。例如裕膀,把剛才的需求調(diào)整一下:現(xiàn)在,如果 x 小于 -1勇哗,取值為“策”昼扛,否則取值為“躲”。取
x=-2
欲诺,有 bug 的程序如下:
x=-2
if (x<-1) "策" else "躲"
這樣一來(lái)抄谐,只要一運(yùn)行,就得到
"策"
這個(gè)結(jié)果是符合需求的扰法,然而程序完全錯(cuò)誤蛹含!表達(dá)式給出的這個(gè)“策”,不是因?yàn)椤皒 小于 -1”塞颁,而是因?yàn)椤皒被賦值成1浦箱,且1被當(dāng)成邏輯真”。這樣一來(lái)祠锣,錯(cuò)誤的程序弄出了看起來(lái)正確的結(jié)果酷窥,這使得調(diào)試非常困難。如果成千上萬(wàn)行程序中藏了個(gè)類似的錯(cuò)誤伴网,一時(shí)通過(guò)了測(cè)試蓬推,直到項(xiàng)目交付之后才爆出問(wèn)題,就更麻煩了澡腾。
結(jié)語(yǔ)
編程語(yǔ)言沸伏,不是誰(shuí)都能設(shè)計(jì)的糕珊。稍有不慎,就會(huì)制造許多“坑”毅糟,甚至還會(huì)把某些“坑”當(dāng)做“特性”红选。R這種東西,拿來(lái)當(dāng)繪圖工具或是計(jì)算器倒是可行留特,卻也不是必須的纠脾。若要拿它做重要的事情玛瘸,最好再三考慮蜕青。寫(xiě)到這里,我已將R卸載了糊渊。