2017-08-02 20:40:03

目录

作用域(Scope)

作用域: 静态和动态

作用域: 一组程序规则,定义识别符号(identifier)和实体(entity)之间的绑定(binding,即名-实映射)。作用域决定了R如何查找一个符号的值。

词法作用域(lexical scoping)

  • (也叫静态作用域) 对象在编译时有一个静态确定的作用域,该对象仅在此作用域内可见,该作用域以外不可访问
  • R广泛采用此法作用域,常见的作用域有函数闭包、附加包命名空间、R工作空间等
  • R会根据这些作用域创建时(而非调用时)的层级关系查找对象求值

动态作用域(dynamic scoping)

  • 编译时只有一个环境,每次求值时,都从这个唯一的环境中查询和更改
  • R根据这些作用域在何处调用,查找对象求值
  • 在非标准求值中,可能会遇到动态作用域

词法作用域的基本概念

  • 名称覆盖 (name masking)

    首先查找函数体内的对象是否有符合对应名称的变量,如果没有的话,就再上升一层继续查找

  • 函数和变量 (functions v.s. variables)

    如果函数和变量的名称一样的话,会根据当时的语法去判断是函数还是变量

  • 全新起始 (a fresh start)

    函数每次运行,其中的变量都是重新建立的,和上次运行没有关系

  • 动态查找 (dynamic lookup)

    R 语言中,对变量的查找是在函数运行的时候进行的,而不是在建立的时候

名称覆盖

x <- 1
f1 <- function(){
    y <- 2
    f2 <- function(){
        z <- 3
        c(x, y, z)
    }
    f2()
}
f1()
## [1] 1 2 3
x <- 1; y <- 4; z <- 5
f1 <- function(){
    y <- 2; z <- 6
    f2 <- function(){
        z <- 3
        c(x, y, z)
    }
    f2()
}
f1()
## [1] 1 2 3

函数和变量

函数和变量一视同仁

f1 <- function(x) x+1
f2 <- function(){
    f1 <- 10
    f1(f1)
}
f2()
## [1] 11
c(c=c)
## $c
## function (...)  .Primitive("c")

全新起始

每次运行f(),会生成一个新的环境

f <- function(){
    if (exists("x")) x <- x + 1 else  x <- 1
    x
}
f()
## [1] 2
f()
## [1] 2

动态查找

  • 一个函数闭包应尽量不依赖外层环境的对象,以免意外修改导致错误
  • 可用codetools::findGlobals()查看非自包含函数的外部依赖

f()的运算过程依赖外部对象x

f <- function() x
x <- 1; f()
## [1] 1
x <- 2; f()
## [1] 2
codetools::findGlobals(f)
## [1] "x"

甚至可以覆写函数/算符

"+" <- `c`
1+1
## [1] 1 1
1:2 + c("a", "b")
## [1] "1" "2" "a" "b"

环境(env)

什么是环境?

环境就是一组名-实绑定。

e <- new.env(); e
<environment: 0x0000000026a45c78>
e$a <- FALSE
e$b <- 1:2
e$c <- e$b
ls.str(e)
a :  logi FALSE
b :  int [1:2] 1 2
c :  int [1:2] 1 2

环境和列表的区别

环境和列表相似

  • 可以用$[[访问对象
  • 可以通过as.listas.environment相互转换

但也有例外

  • 一个环境中的所有对象名称都是唯一的
    • 但同一个名字可以用在不同环境中
  • 环境中的对象名称是无序的
    • 不存在“第几个对象”
  • 环境有上级(父parent)环境
    • 唯一例外是空环境
  • 环境有引用语义(reference semantics)
    • 修改副本环境,原环境也会被改变

环境层级

  • 特殊环境

    • globalenv():
      全局环境,即交互工作空间
    • baseenv():
      基础环境,base包引入的环境
    • emptyenv(): 空环境
    • environment(): 当前环境
  • search()
    可查看globalenv()的所有父环境
  • searchpaths()可查看搜索路径
  • parent.env()查看父环境

searchpaths()
## [1] ".GlobalEnv"                   "/usr/lib/R/library/stats"    
## [3] "/usr/lib/R/library/graphics"  "/usr/lib/R/library/grDevices"
## [5] "/usr/lib/R/library/utils"     "/usr/lib/R/library/datasets" 
## [7] "/usr/lib/R/library/methods"   "Autoloads"                   
## [9] "/usr/lib/R/library/base"

搜索路径

环境操作

  • 创建环境e

    e <- new.env()
  • 环境内创建对象

    e$a <- FALSE
    e[['b']] <- 1:2
    assign(".c", e$b, envir=e)
  • 查看环境(默认不显示e$.c)

    ls.str(e, all.names=TRUE)
    .c :  int [1:2] 1 2
    a :  logi FALSE
    b :  int [1:2] 1 2
  • 判断某对象是否存在

    exists(".c", envir=e)
  • 两个环境是否相同

    identical(environment(), globalenv())
  • 找父环境

    parent.env(e)
    <environment: R_GlobalEnv>
  • 删除

    rm("a", envir=e)

跨环境递归查找

e1 <- new.env(parent=globalenv())
e2 <- new.env(parent=e1)
e3 <- new.env(parent=e2)
e1$x <- 1
e2$y <- 2
e3$x <- 3
e1; e2; e3

<environment: 0x3e00da0> <environment: 0x3d9c6b8> <environment: 0x3d45810>

用一个递归算法,层层向上查找

find_up <- function(name, env){
    if (exists(name, envir=env, inherits=FALSE))
        env
    else find_up(name, parent.env(env))
}
find_up("x", e3)
## <environment: 0x3d45810>
find_up("y", e3)
## <environment: 0x3d9c6b8>
find_up("plot", e3)
## <environment: package:graphics>
## attr(,"name")
## [1] "package:graphics"
## attr(,"path")
## [1] "/usr/lib/R/library/graphics"

函数环境

函数环境的类型

  • 闭包环境(enclosing environment)

    • 当函数被创建出来,有且只有一个闭包环境
    • 闭包环境是不变的,决定了函数如何查找值
  • 绑定环境 (binding environment)

    • 通过<-将函数定义绑定给一个名称,定义出一个绑定环境
    • 绑定环境决定我们如何通过名称查到函数
  • 执行环境 (execution environment)

    • 调用函数,产生一个临时的执行环境,存储执行过程中产生的变量
  • 调用环境 (calling environment)

    • 每个执行环境关联一个调用环境

闭包环境和绑定环境

  • environment()获得函数的闭包环境
f <- function(x) x+y
environment(f)
## <environment: R_GlobalEnv>
# f的闭包环境是globalevn()
  • 绑定环境取决于函数符号的位置
    • 附加包较特殊,拥有package和namespace两套环境。引入后,产生imports和namespace环境
    • globalenv()中改写附加包函数,不改变其原始绑定环境
e <- new.env(parent=globalenv())
e$f <- function() 0
# e$f的绑定环境为e,但闭包环境是globalenv()

执行环境

f <- function(x){
    cat("执行环境:\n")
    print(environment())
    print(list("0> init"=
        ls.str(environment())))
    a <- 10
    print(list("1> a <- 10"=
        ls.str(environment())))
    o <- x + a
    print(list("2> o <- x + a"=
        ls.str(environment())))
    return(o)
}
f(5)
## 执行环境:
## <environment: 0x3e8bfa8>
## $`0> init`
## x :  num 5
## 
## $`1> a <- 10`
## a :  num 10
## x :  num 5
## 
## $`2> o <- x + a`
## a :  num 10
## o :  num [1:2] 5 10
## x :  num 5
## [1]  5 10

调用环境

a <- 1
f <- function(){
    a <- 2
    cat("f()\n")
    print(environment())
    cat("执行:", get("a", environment()), "\n")
    print(parent.frame())
    cat("调用:", get("a", parent.frame()), "\n")
    g <- function() {
        a <- 3
        cat("\ng()\n")
        print(environment())
        cat("执行:", get("a", environment()), "\n")
        print(parent.frame())
        cat("调用:", get("a", parent.frame()), "\n")
    }
    g()
}
local({cat(".GlobalEnv\n")
    print(environment())
    cat("执行:", get("a", environment()), "\n")
    print(parent.frame())
    cat("调用:", get("a", parent.frame()), "\n")})
## .GlobalEnv
## <environment: R_GlobalEnv>
## 执行: 1 
## <environment: R_GlobalEnv>
## 调用: 1
f()
## f()
## <environment: 0x429f3f8>
## 执行: 2 
## <environment: R_GlobalEnv>
## 调用: 1 
## 
## g()
## <environment: 0x47890f8>
## 执行: 3 
## <environment: 0x429f3f8>
## 调用: 2

引用语义

# 函数modify_a将一个对象的元素"a"改为2
modify_a <- function(x) invisible(x$a <- 2)
  • 列表采用"复制后修改",原对象不改变
lst <- list(a=1)
lst$a
## [1] 1
modify_a(lst)
lst$a
## [1] 1
  • 环境有引用语义,原对象会改变
env <- new.env()
env$a <- 1
ls.str(env)
## a :  num 1
modify_a(env)
ls.str(env)
## a :  num 2