捕獲表達式以後,下一步就是對其進行求值,能夠用 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函數