1. 函数调用的各种形式

在R语言中,有两条简明的理解R程序的原则:

任何成分都是R的对象(变量、函数等等);
任何活动都是调用函数(求子集、四则运算、比较、函数调用等)。

前缀形式

这也是一般的格式,如fsub(5, 2)

中缀形式

二元运算符实际上都是函数,5 - 2的写法是中缀形式, 等同于-(5, 2)。因为-不是合法的R变量名(函数名), 所以在写成前缀形式时要用反向单撇号`保护。 这样,在lapply等泛函中可以使用 + 这样的四则运算作为输入的操作。 如:

  1. > `-`(c(1:2), c(5:4))
  2. [1] -4 -2
  3. > 1:2 - 5:4
  4. [1] -4 -2
  5. > sapply(1:2, `-`, 3)
  6. [1] -2 -1

用户也可以自己定义函数名如%x% 这样的中缀函数, 可以用中缀格式调用。

替换形式

对属性的修改经常这样写:

  1. x <- 1:2
  2. names(x) <- c("a", "b")
  3. x
  4. ## a b
  5. ## 1 2

ps:这里不太能理解作者的用意,用前缀形式举例。但大概意思应该是,对于对象的属性修改,过程其实都是替换形式,也即都会对本来的对象创建副本。

即制作了x的副本,调用names<-函数, 将x重新绑定到names<-函数的返回值。

  1. x <- 1:2
  2. `*tmp*` <- x
  3. x <- `names<-`(x, c("a", "b"))
  4. rm(`*tmp*`)
  5. x

特殊形式

x[1], x[[1]] 这些取子集或元素以及修改, (), {},if 结构、for 循环等本质上也是函数调用, 只不过用了特殊的语法。 这些函数在R中都是初等函数(primitive functions)。 初等函数仅在基本R中定义, 是由C代码实现的, 用户无法访问其三个部分。

所谓R 的万物皆可前缀形式(我到现在也无法理解为何需要这样操作),我们也可以将这些特殊形式也通过前缀形式使用:

  1. > `[`(x, 1)
  2. a
  3. 5
  4. > x[1]
  5. a
  6. 5
  7. > (x[1] <- "999")
  8. [1] "999"
  9. > (`[<-`(x,1,999))
  10. a b
  11. "999" "6"
  12. > `for`(i, 1:3, print(i))
  13. [1] 1
  14. [1] 2
  15. [1] 3

ps:不难发现,二者还是有一些细微的差别的。

2. 嵌套定义与句法作用域(lexical scoping)

介绍

和python 一样,我们也可以在R 的函数内容定义函数:

  1. x <- -1
  2. f0 <- function(x){
  3. f1 <- function(){
  4. x + 100
  5. }
  6. f1()
  7. }

其中内嵌的函数f1() 称为一个closure(闭包)。

内嵌的函数体内在读取某个变量值时, 如果此变量在函数体内还没有被赋值, 它就不是局部的, 会向定义的外面一层查找, 外层一层找不到,就继续向外查找。 上面例子f1()定义中的变量x不是局部变量, 就向外一层查找, 找到的会是f0的自变量x,而不是全局空间中x。 如

  1. f0(1)
  2. ## [1] 101

我们先前说过,,这样的变量查找规则叫做句法作用域(lexical scoping), 即函数运行中需要使用某个变量时, 从其定义时的环境向外层逐层查找, 而不是在调用时的环境中查找。

比如下面的例子:

  1. f0 <- function(){
  2. f1 <- function(){
  3. x <- -1
  4. f2 <- function(){
  5. x + 100
  6. }
  7. f2()
  8. }
  9. x <- 1000
  10. f1()
  11. }
  12. f0()

运行结果是99,这是因为函数调用使用的是离变量最近环境中f1 中定义的-1。另外函数调用时查找变量是查找其定义时的变量对应的存储空间, 而不是定义时变量所取的历史值。 函数运行时在找到某个变量对应的存储空间后, 会使用该变量的当前值,而不是函数定义的时候该变量的历史值。 这种规则称为动态查找(dynamic lookup)
比如:

  1. f0 <- function(){
  2. f1 <- function(){
  3. x <- -1
  4. f2 <- function(){
  5. x + 100
  6. }
  7. x <- 99
  8. f2()
  9. }
  10. x <- 1000
  11. f1()
  12. }
  13. 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])
}