在R语言的文献中,函数被证实地称为“闭包(closure)”。函数不仅包括参数和函数体,也包括它的“环境(environment)”。环境是由创建函数时出现的对象集构成。
►顶层环境
> w <- 12> f <- function( y ){
+ d <- 8
+ h <- function(){
+ return ( d*(w+y) )
+ }
+ return ( h() )
+ }
> environment( f )
<environment: R_GlobalEnv>
此例中,函数f()是在顶层(解释器命令提示符下)构建的,于是它处于顶层环境。顶层环境在R的输出结果里表示为R_GlobalEnv,不过,它常与R代码的.GlobalEnv混淆。如果以批处理方式运行R程序,也会被认为在顶层。
函数ls()会把某个环境中的所有对象列举出来。如果,在顶层调用它,就会得到顶层的环境下的对象名单。用ls.str()可以获得更多的信息。
在函数中调用不带参数的ls()会返回当前的局部变量(包括参数)。使用envir参数,ls会输出函数调用链中任何一个框架的局部变量名。
►变量作用域的层次
在R语言中,因为函数属于对象,在函数中定义一个函数是可以实现的,有时从面向对象编程封装的目的来看,这样做也是可取的。我们可以在任何地方都可以创建对象。
正如变量d一样,h()属于f()的局部对象。在这种情况下,变量作用域的层次问题是有意义的。根据R语言的定义,f()的局部变量d,相对于h()来说是局部变量。对于参数y,结论也一样,因为在R语言中,参数也被看作局部变量。类似的,变量作用域的分层特性意味着,既然w是f()的全局变量,那么它也是h()的全局变量。实际上,我们在函数h()中也用到了w。
就环境而言,h()的环境包括在它创建时定义的所有对象,也就是,如果f()被调用多次,h()也多次创建,但它又会随着f()的返回而消失。h()对f()来说是局部的,且在顶层环境中不可见。
在层次中发生命名冲突时可以的(尽管这样做并不可取),在这种情况下,优先使用最里层的变量。像这种以继承的方式创建的环境一般靠它们的内存地址来引用。
> f <- function( y ){
+ d <- 8
+ return ( h() )
+ }
> h <- function(){
+ return ( d * ( w +y ) )
+ }
> f( 1 )
Error in FUN(left, right) : non-numeric argument to binary operator
这样并不可行,由于h()定义于顶层,变量d不再在h()的环境中,于是,代码运行出错。更糟糕的是,如果碰巧在顶层环境下存在一个不相关的变量d,我们不会得到出错提示,只会返回一个错误的结果。
你也许会想到为什麽上例中R没有提示变量y不存在。这是因为前面提到过的惰性求值原则,即只有当需要某个变量值的时候,R才会计算它。在这个例子中,R已经遇到了一个由d引起的错误而不会继续求y的值。
以上代码的修正方法是把d和y设置为参数:
> f <- function( y ){
+ d <- 8
+ return ( h( d, y ) )
+ }
> h <- function( dd, yy ){
+ return ( dd * ( w +yy ) )
+ }
> f( 1 )
[1] 104
下面的代码:
> w <- 2
> f <- function( y, hh ){
+ d <- 8
+ print( environment( hh ) )
+ return ( hh( d, y ) )
+ }
> h <- function( dd, yy ){
+ return ( dd * ( w +yy ) )
+ }
> f( 1, h )
<environment: R_GlobalEnv>[1] 24
在f()执行时,形式参数dd与实际参数d相匹配。由于参数被看作局部变量,你也许猜测dd的环境与顶层环境不一样。但闭包包括了环境,所以dd与h的环境一致。
►函数(几乎)没有副作用
函数式编程哲学的另一个特征是,函数不会修改非局部变量。也就是说,一般情况下,使用函数时不会有副作用。函数的代码可以读,但不能写其非局部变量。代码看似可以给这些变量重新复制,但实际上这种行为只会影响它们的备份,而不是变量本身。
注:R对任何函数的调用都会创建一个框架(frame)。该框架包括函数中创建的局部变量,以及在一个环境求值时组合创建的新环境。
> w <- 12
> f <- function( y ){
+ d <- 8
+ w <- w + 1
+ y <- y - 2
+ print( w )
+ h <- function(){
+ return ( d * ( w + y ) )
+ }
+ return ( h() )
+ }
> t <- 4
> f( t )
[1] 13[1] 120
> w
[1] 12
> t
[1] 4
所以,顶层的w并没有改变,即使看起来在f()中被修改了。只有f()中w的局部被跟发生了改变。类似地,顶层的变量t也没有改变,尽管与它相关联的参数y发生了变化。实际上,局部变量w与相应的全局变量共享一个内存地址,直到局部变量的数值发生了变化。这种情况下,会分配给局部变量w新的内存地址。
►关于全局变量
R语言中全局变量的使用比想象中的要多。R语言自身内部大量使用了全局变量,物理上其C语言代码还是R例程,对于这点你也许会感到惊讶。例如,超赋值运算符<<-用于许多R语言函数中(尽管通常只对上一级环境层次中的变量进行写操作)。线程化(Threaded)代码和GPU代码倾向于大量使用全局变量,这为并行对象提供了主要的通信途径。两者均用于编写高性能程序。
►关于“闭包”
R语言中“闭包”包含了函数的参数、函数体以及调用时的环境。有一种编程方法是用闭包包括环境,这种编程方法使用的特性也叫做“闭包”。
闭包包含了一个可创建局部变量的函数,并创建另一个函数可以访问该变量。