1. 函数调用的各种形式
在R语言中,有两条简明的理解R程序的原则:
任何成分都是R的对象(变量、函数等等);
任何活动都是调用函数(求子集、四则运算、比较、函数调用等)。
前缀形式
这也是一般的格式,如fsub(5, 2)
。
中缀形式
二元运算符实际上都是函数,5 - 2的写法是中缀形式, 等同于-
(5, 2)。因为-不是合法的R变量名(函数名), 所以在写成前缀形式时要用反向单撇号`保护。 这样,在lapply等泛函中可以使用 + 这样的四则运算作为输入的操作。 如:
> `-`(c(1:2), c(5:4))
[1] -4 -2
> 1:2 - 5:4
[1] -4 -2
> sapply(1:2, `-`, 3)
[1] -2 -1
用户也可以自己定义函数名如%x%
这样的中缀函数, 可以用中缀格式调用。
替换形式
对属性的修改经常这样写:
x <- 1:2
names(x) <- c("a", "b")
x
## a b
## 1 2
ps:这里不太能理解作者的用意,用前缀形式举例。但大概意思应该是,对于对象的属性修改,过程其实都是替换形式,也即都会对本来的对象创建副本。
即制作了x的副本,调用names<-
函数, 将x重新绑定到names<-函数的返回值。
x <- 1:2
`*tmp*` <- x
x <- `names<-`(x, c("a", "b"))
rm(`*tmp*`)
x
特殊形式
x[1], x[[1]] 这些取子集或元素以及修改, (), {},if 结构、for 循环等本质上也是函数调用, 只不过用了特殊的语法。 这些函数在R中都是初等函数(primitive functions)。 初等函数仅在基本R中定义, 是由C代码实现的, 用户无法访问其三个部分。
所谓R 的万物皆可前缀形式(我到现在也无法理解为何需要这样操作),我们也可以将这些特殊形式也通过前缀形式使用:
> `[`(x, 1)
a
5
> x[1]
a
5
> (x[1] <- "999")
[1] "999"
> (`[<-`(x,1,999))
a b
"999" "6"
> `for`(i, 1:3, print(i))
[1] 1
[1] 2
[1] 3
ps:不难发现,二者还是有一些细微的差别的。
2. 嵌套定义与句法作用域(lexical scoping)
介绍
和python 一样,我们也可以在R 的函数内容定义函数:
x <- -1
f0 <- function(x){
f1 <- function(){
x + 100
}
f1()
}
其中内嵌的函数f1() 称为一个closure(闭包)。
内嵌的函数体内在读取某个变量值时, 如果此变量在函数体内还没有被赋值, 它就不是局部的, 会向定义的外面一层查找, 外层一层找不到,就继续向外查找。 上面例子f1()定义中的变量x不是局部变量, 就向外一层查找, 找到的会是f0的自变量x,而不是全局空间中x。 如
f0(1)
## [1] 101
我们先前说过,,这样的变量查找规则叫做句法作用域(lexical scoping)
, 即函数运行中需要使用某个变量时, 从其定义时的环境向外层逐层查找, 而不是在调用时的环境中查找。
比如下面的例子:
f0 <- function(){
f1 <- function(){
x <- -1
f2 <- function(){
x + 100
}
f2()
}
x <- 1000
f1()
}
f0()
运行结果是99,这是因为函数调用使用的是离变量最近环境中f1 中定义的-1。另外函数调用时查找变量是查找其定义时的变量对应的存储空间, 而不是定义时变量所取的历史值。 函数运行时在找到某个变量对应的存储空间后, 会使用该变量的当前值,而不是函数定义的时候该变量的历史值。 这种规则称为动态查找(dynamic lookup)
。
比如:
f0 <- function(){
f1 <- function(){
x <- -1
f2 <- function(){
x + 100
}
x <- 99
f2()
}
x <- 1000
f1()
}
f0()
此时的结果就是199,这是因为,在调用f2 之间,其只会访问变量x 的当前值99,而不是历史值-1。
句法作用域不仅适用于查找变量, 也适用于函数体内调用别的函数时查找函数。 查找函数的规则与查找变量规则相同。
辅助嵌套函数
可以称之为高仿的嵌套了:
solve.sqe <- function(x){
fd <- function(a, b, c) b^2 - 4*a*c
d <- fd(x[1], x[2], x[3])
if(d >= 0){
return( (-x[2] + c(1,-1)*sqrt(d))/(2*x[1]) )
} else {
return( complex(real=-x[2], imag=c(1,-1)*sqrt(-d))/(2*x[1]) )
}
}
在这个函数中内嵌的函数fd仅起到一个计算二次判别式的作用, 没有用到任何的闭包特性, 其中的形参变量a, b, c都是局部变量。
这样的内嵌函数与直接在全局空间中定义的函数区别不大, 只有一条区别: 只能在定义它的函数内运行, 不能被直接调用, 可以看成是函数内的私有函数, 可以避免名字冲突。
我们可以修改一下:
solve.sqe <- function(x){
fd <- function(a, b, c){
d <- b^2 - 4*a*c
if(d >= 0){
return( (-x[2] + c(1,-1)*sqrt(d))/(2*x[1]) )
} else {
return( complex(real=-x[2], imag=c(1,-1)*sqrt(-d))/(2*x[1]) )
}
}
fd(x[1], x[2], x[3])
}