捕获表达式以后,下一步就是对其进行求值,能够用 eval( ) 函数完成这个工做。
例如,若是在控制台键入 sin(1) 而且按下回车键,会当即显示出结果:
sin(1)
## [1] 0.841471
为了控制 sin(1) 的计算过程,咱们可使用 quote( ) 捕获此表达式,而后调用
eval( ) 对它进行计算:
call1 <- quote(sin(1))
call1
## sin(1)
eval(call1)
## [1] 0.841471
咱们能够捕获任何一个语法正确的表达式,这使得咱们可以用 quote( ) 捕获一个使
用了未定义变量的表达式:
call2 <- quote(sin(x))
call2
## sin(x)
在 call2 里,sin(x) 使用了一个未定义变量 x。若是直接对其进行求值, R 便会报错:
eval(call2)
## Error in eval(expr, envir, enclos): 找不到对象'x'
这个错误结果与在 x 未定义的状况下直接运行 sin(x) 相似:
sin(x)
## Error in eval(expr, envir, enclos): 找不到对象'x'
直接在控制台运行与使用 eval( ) 的区别在于,eval( ) 容许咱们提供一个列表来
计算给定表达式。在这个例子中,咱们不须要建立一个变量 x,只要提供一个包含 x 的临
时列表,表达式便会在列表中搜索相应符号:
eval(call2, list(x = 1))
## [1] 0.841471
或者,也能够在 eval( ) 中添加一个用于搜索符号的环境。下面咱们建立一个新环境 e1,
而且在 e1 中建立一个取值为 1 的变量 x,而后在 e1 中将 eval( ) 做用在调用 call2 上:
e1 <- new.env()
e1$x <- 1
eval(call2, e1)
## [1] 0.841471
相同的逻辑也能够应用于被捕获的表达式含有多个未定义变量的状况:
call3 <- quote(x ^2 + y ^2)
call3
## x^2 + y^2
在彻底没有指定未定义符号时,对表达式进行求值会产生错误:
eval(call3)
## Error in eval(expr, envir, enclos): 找不到对象'x'
只指定了部分符号时,一样会报错:
eval(call3, list(x = 2))
## Error in eval(expr, envir, enclos): 找不到对象'y'
只有指定了表达式中的全部变量的值,eval( ) 才会正确运行并返回一个值:
eval(call3, list(x = 2, y = 3))
## [1] 13
eval(expr, envir, enclos) 的计算模式与调用函数相同。函数体为 expr,执
行环境为 envir。若是 envir 以列表形式给出,封闭环境即是 enclos,不然,封闭环境
就是 envir 的父环境。
这个模式隐含了符号查找的确切方式。假设咱们在一个环境中执行 call3。因为 e1 只
包含变量 x,计算并不能完成:
e1 <- new.env()
e1$x <- 2
eval(call3, e1)
## Error in eval(expr, envir, enclos): 找不到对象'y'
接下来,建立一个新环境,其父环境为 e1,而且包含变量 y。如今,若是在 e2 里执
行 call3,x 和 y 都能被找到,也能够完成计算:
e2 <- new.env(parent = e1)
e2$y <- 3
eval(call3, e2)
## [1] 13
在前面的代码中,eval(call3, e2) 试图计算 call3,其中 e2 为执行环境。如今,
咱们来理一遍计算过程,从而对其工做方式有一个更好的理解。计算过程表现为沿着由
pryr::call_tree( )产生的调用树进行递归的过程:
pryr::call_ _tree(call3)
## \- ()
## \- `+
## \- ()
## \- `^
## \- `x
## \- 2
## \- ()
## \- `^
## \- `y
## \- 2
具体计算过程为:首先,寻找一个名为 + 的函数。在 e2 和 e1 中没能找到 + ,而后在
基础环境(baseenv( ))中找到了,全部的基础运算符都定义在基础环境中。接下来, + 需
要对其参数进行计算,因而寻找另外一个名为 ^ 的函数,其路径与 + 相同。以后, ^ 一样需
要计算其参数,因此又在 e2 中寻找符号 x。环境 e2 中没有变量 x,因而又在其父环
境 e1 中搜索,而且找到了 x。最后,在 e2 中找到了符号 y。当调用所需的参数都准备齐
全了,即可以计算出结果。
另外一种方法是给 envir 参数提供一个列表和一个封闭环境:
e3 <- new.env()
e3$y <- 3
eval(call3, list(x = 2), e3)
## [1] 13
计算过程由列表生成的执行环境开始,其父环境就是咱们指定的 e3。以后的过程与前
面的例子相同。
咱们所作的本质上都是函数调用,quote( ) 和 substitute( ) 能够捕获一切表达
式,包括赋值和其余不像函数调用的操做。事实上,例如 x <- 1 本质上就是调用 < -,其
参数为( x, 1 ),length(x) <- 10 本质上就是调用 length <-,参数为( x, 10 )。
为了说明这一点,咱们再举一个例子,而且建立一个新的变量。
在下面的例子中,咱们利用一个列表生成执行环境,并以 e3 做为封闭环境:
eval(quote(z <- x + y + 1), list(x = 1), e3)
e3$z
## NULL
结果显示,z 不是在 e3 中建立的,而是在由列表建立的一个临时执行环境中建立的。
若是咱们指定 e3 为执行环境,那么变量将会在 e3 中建立:
eval(quote(z <- y + 1), e3)
e3$z
## [1] 4
综上所述,eval( ) 的工做方式与函数调用行为极其类似,可是 eval( ) 容许咱们
经过调整表达式的执行环境和封闭环境来定制计算过程,然而,这是一把双刃剑,能够帮
助咱们作一些事情,例如 substitute( ),也会出现一些麻烦事:
eval(quote(1 + 1), list(`+` = `-`))
## [1] 0函数