全部版块 我的主页
论坛 计量经济学与统计论坛 五区 计量经济学与统计软件
804 1
2015-07-31

R是一门古怪的语言,这一点没什么好否认的。它的古怪有好有坏,在不同人眼中也可能是好事或坏事。R是受S语言影响发展起来的,S语言诞生于贝尔实验室。后来AT&T被分拆的时候,这个实验室被拆为今天的贝尔实验室(已经没什么名气了)和AT&T实验室。如前文所说,这个夏天在AT&T实验室呆着,S语言的一位作者Rick是我的办公室邻居,另一位作者Allan暑假里退休了,第三位作者是John Chambers,他早已离开实验室去高校了。S诞生在(文本)数据堆里,而R诞生之后很快走向了合作开发,这一系列历史给它带来了一些看似古怪的特征。

赋值

赋值符号在绝大多数语言中都没什么好讨论的,因为就是一个等号而已。在R社区,这一点却被讨论来讨论去,主要原因是箭头赋值符号<-的存在。箭头来源于贝尔实验室早年的某台古董机器上有一个下划线的键,但打出来显示的是箭头。S祖先们认为箭头是一个很形象的赋值符号,于是下划线被采纳下来,甚至后来衍生出右箭头(->)这样更奇怪的赋值符号(表示把左边的值赋给右边的变量),还有双箭头(<<-)表示给上一层环境中的变量赋值。

最初S代码中,下划线本身就是赋值符号,例如x_1表示把1赋值给x。我大三的时候还用S-Plus写了不少下划线赋值的S代码。后来R core们做了一个艰难的决定,允许等号作为赋值符号,废弃了下划线的赋值功能,但下划线的传统仍然无处不在,例如Emacs / ESS中默认情况下敲下划线会被替代为左箭头(为了得到真的下划线需要连续按两次下划线),尽管下划线本身已经不能直接显示为箭头,ESS仍然想昨日重现,通过软件方式强行替代之。

我不止一次地说过,我是坚定的等号党。因为等号有赋值功能,大多数语言都用等号(没见其它语言的程序员抱怨等号不形象),等号对我来说不存在歧义,让我的代码更安全。反对党(也就是箭头党)的理由通常是等号存在二义性:它既可以赋值,也可以传递函数参数,如:

x = 1:10length(x = 1:10)

可是几乎所有语言都这样,也没有见那些语言的程序员对此有抱怨。规则很简单:如果等号出现在单独的环境中,它就是赋值;如果写在函数参数位置,它就是传参数。但R的古怪让这个简单规则也可以变得很难判断,例如:

length((x = 1:10))length({x = 1:10})

因为()和{}将表达式与外面的环境隔离开来,它们被解析的时候先单独运行,所以实际上x在自己的环境里被赋值了;然后()和{}会返回整个表达式的结果,这个结果再传给length()函数。

箭头在任何地方的意思都是赋值(在当前环境下),它可以被写在任何地方,包括函数参数的位置,如:

length(x <- 1:10)

这句代码先对x赋值,然后把整个表达式的值传递给length()。我们可以把它写得更晕:

length(x = y <- 1:10)

此时等号表示传参数,而y无论如何都会被赋值。在函数参数位置上赋值通常有一举两得的效果,也就是把两件事情写在一行上,之所以能一举两得,主要是利用了箭头的副作用;如果是初学者,这种写法最好避免,首先追求代码的清晰性,避免产生副作用的代码。因为箭头无论何地都可以赋值,要是用错了也不会报错,这种错误往往难以意识到。例如我们创建一个向量,元素为1和2,元素名字分别为a和b,如果不小心写成这样:

c(a = 1, b <- 2)  # 本来应该是c(a = 1, b = 2)

你可能不会意识到第二个元素是没有名字的,并且这句话带来一个副作用,就是悄悄给b赋了值。

如果你像我一样有时候写代码不爱打空格,那么还有一个更可怕的潜在错误,要是不小心犯了的话可能很久都查不出来。我们写几个逻辑表达式:x大于5,x小于3,x小于-3。你可能想,这个太简单了,操起键盘就写:

x>5x<3x<-3

因为你的懒惰,小于号和负号悄磨叽走到了一起,不小心形成了具有强大法力的赋值箭头,你并没有完成x与-3的逻辑大小比较,而是给x赋值为3。当这几行代码在这里摆着的时候你可能觉得很容易看出来,可是当你玩了两个小时数据之后,想随手看一下df数据框中x变量小于-1的值有多少个,你可能会写出sum(df$x<-1)这样的语句,相应的结果是,你没得到df$x小于-1的总数,而是把df数据里x这一列给修改为1了。如果df是一个很大的数据,或你辛辛苦苦处理了半天才得来的数据,你就哭吧。我对这个诡异的案例印象深刻,是因为我亲眼看见过两例别人的错误,在那之后我明知有这样的危险,但自己还是傻不愣登毁了一次自己的数据。

对右箭头赋值,我的想象是这样:某天某祖先写了一长段代码,但没有事先写上把这段代码的结果赋值保留下来,悔得肠子都青了,只好敲回车任凭程序在那儿跑,跑完了得不到返回值,于是该祖先发明了一个右箭头,这样即使先写了一段代码也不用怕,因为可以最后加上-> x就把前面的返回值赋给x了。我不习惯阅读这种事后赋值的代码,就像读侦探小说似的,到最后才发现代码创建了一个变量。

这种“后悔”的想象还可以继续:R中有一个特殊的变量叫.Last.value,它总是保存最近一次运行的最后一个结果,即使你上一条代码没有赋值保存,你仍然可以通过.Last.value去获取。这也意味着,无论你跑什么程序,R都会随时盯着你的返回结果,把它赋值给.Last.value。

命名风格

R core的主要命名风格是以点分隔词,例如t.test,这与早年时下划线有赋值含义有关,另外我猜想也是懒惰,因为点只需要按键一次,而其它命名法都需要按Shift键,如camelCase。不过这个也不绝对,R里面仍然有些下划线命名的函数如seq_along,或驼峰命名如summaryRprof。这里面有多人合作时的个人风格,更重要的可能是S3泛型函数的影响。S3函数的特点就是“主函数.类名”,如summary.lm,它根据传递进来的对象的类来匹配具体的子函数。因为在泛型函数中,点是有特殊意义的,所以我们要小心点(这里注意断句),为了安全起见,最好干脆避免点,免得跟泛型函数扯上关系,尤其是包的作者在写函数时。

在其它很多语言如JavaScript中,点通常表示取一个对象的子元素或者应用方法,如x.toString()。R的点不存在这个问题(要达到同样的效果,一般用$),它除了可能有S3的意思,没有其它特殊含义。考虑到其它语言以及S3两个原因,我最终投奔了下划线命名法(foo_bar),次要原因是我觉得下划线把两个单词分得更开,比fooBar易读。

考虑R中有成千上万的函数命名,某些对象命名可以理解,但仍然透露出某种不规范的痕迹。比如seq()是S3泛型函数,而同时又存在seq.int和seq_along这两种风格的函数,并且前者并不是seq()应用在int类上的函数!

每一门语言都有一些历史糟粕,R作为一群统计学家维护的语言,从规范来说槽点很多,但事情的另一面是,他可以让什么最小惊讶原则见鬼去,老夫今天就是要写一个函数把混合效应模型中的随机效应算完打印出来。他的随意对应用统计者来说,可能恰好也是好事。没有这看似乱糟糟的各种贡献,R的发展也许会慢很多。无论如何,对如今已经趋于成熟的R,我们作为用户还是应该尽量朝规范的方向走。

语法

以for循环为例,很多语言都是教条式的for (i=0; i<10; i++)循环,而R是for (i in x),这个x可以是很多种对象,例如1:10,或10:1,或c('a', 'c', 'b'),或list(a = 1, b = 'fgh'),等等。这种让循环变量在一组对象中循环的做法,我猜想可能借鉴自bash脚本的语法,如

for i in `ls *.csv`do  echo $idone

它为啥要参照bash脚本的循环语法而不是C语言的语法,可能跟贝尔实验室的数据处理传统有关。至今AT&T实验室仍然跑着大量的bash脚本,处理大量的文本数据(循环逐个处理每个文件),这一点我在那里感触太深了。同样诞生于贝尔实验室的Awk,其循环语法也借鉴了bash的语法(C语法也保留着),可以在一个数组中循环。


二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

全部回复
2015-8-3 10:06:52
https://bbs.pinggu.org/thread-3830281-1-1.html
R的若干基因及争论 二
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群