當咱們在控制檯輸入一個表達式並按下回車鍵時, R 會執行表達式並顯示輸出結果。
以下例所示:ide
rnorm(5)
## [1] 0.54744813 1.15202065 0.74930997 -0.02514251
## [5] 0.99714852
這裏生成了 5 個隨機數。subset( ) 神奇的地方就在於它調整了參數被計算的環境,
分兩步完成,首先捕獲表達式,而後調整表達式的計算。
1.將表達式捕獲爲語言對象
捕獲表達式意味着防止表達式被執行,而將其自己存儲爲變量的形式。具備這個功能
的是函數 quote( ),咱們能夠調用 quote( )來捕獲表達式:
call1 <- quote(rnorm(5))
call1
## rnorm(5)
上述代碼返回代碼自己,而不是輸出 5 個隨機數。咱們能夠用 typeof( ) 和 class( )
查看結果對象 call1 的類型和類:
typeof(call1)
## [1] "language"
class(call1)
## [1] "call"
能夠看到 call1 本質上是一個語言對象,而且是一個函數調用。咱們還能夠在quote( )
中寫一個函數名:
name1 <- quote(rnorm)
name1
## rnorm
typeof(name1)
## [1] "symbol"
class(name1)
## [1] "name"
結果倒是一個符號(或名稱)而不是函數調用。
實際上,quote( ) 捕獲到函數調用時會返回調用,而捕獲到變量名時則會返回一個
符號。惟一的要求是代碼的有效性:即只要代碼語法正確,quote( ) 就會返回表示被捕
獲表達式自己的語言對象。
即使函數不存在或變量未定義,也能夠捕獲表達式自己:
quote(pvar)
## pvar
quote(xfun(a =1:n))
## xfun(a = 1:n)
在上述語言對象中,pvar、xfun 和 n 可能都是未定義的,可是依然能夠對其調用
quote( )函數。
理解變量和符號對象的區別,以及函數和調用對象的區別很是重要。變量是對象的名稱,
而符號對象就是名稱自己。函數是能夠被調用的對象,而調用對象是不會被計算的,它表示這個
函數調用的語言對象。這個例子中,rnorm( ) 就是一個能夠被調用的函數(例如,rnorm(5)
返回 5 個隨機數),可是 quote(rnorm) 返回一個符號對象,quote(rnorm(5)) 返回一個調
用對象,這二者都表示語言自己。
將調用對象轉換成列表,以便查看它的內部結構:
as.list(call1)
## [[1]]
## rnorm
##
## [[2]]
## [1] 5
結果代表本次調用由兩部分組成:函數符號和一個參數。咱們能夠從調用對象中提取
這兩個對象:
call1[[1]]
## rnorm
typeof(call1[[1]])
## [1] "symbol"
class(call1[[1]])
## [1] "name"
call1 的第 1 個元素是一個符號。
call1[[2]]
## [1] 5
typeof(call1[[2]])
## [1] "double"
class(call1[[2]])
## [1] "numeric"
call1 的第 2 個元素是一個數值。從前面的例子中,咱們知道 quote( ) 將一個變量
名捕獲爲符號對象,將一個函數調用捕獲爲調用對象,這二者都是語言對象。和典型的數
據結構同樣,咱們能夠用 is.symbol( )/is.name( )和 is.call( )分別檢查對象是
否爲符號對象或調用對象。更通常地,能夠用 is.language( )同時檢查符號和調用。
還有一個問題, 「若是咱們對一個字面值使用 quote( )會發生什麼呢?例如數字或字
符串?」下面的代碼建立了一個數值 num1,使用了 quote( )的數值 num2:
num1 <- 100
num2 <- quote(100)
二者的輸出徹底相同:
num1
## [1] 100
num2
## [1] 100
事實上,它們擁有徹底相同的值:
identical(num1, num2)
## [1] TRUE
所以,quote( )沒有把字面值(例如數字、邏輯值、字符串等)轉換爲語言對象,
而是使其保持原樣。然而,把幾個字面值組合成一個向量的表達式仍會被轉換爲語言對象。
示例以下:
call2 <- quote(c("a", "b"))
call2
## c("a", "b")
這是由於 c( ) 也是一個函數,它能夠將值和向量組合在一塊兒。此外,若是用 as.list( )
查看錶示調用的列表,能夠看到調用的結構:
as.list(call2)
## [[1]]
## c
##
## [[2]]
## [1] "a"
##
## [[3]]
## [1] "b"
調用內部的元素類型可經過 str( ) 查看:
str(as.list(call2))
## List of 3
## $ : symbol c
## $ : chr "a"
## $ : chr "b"
另外一個值得注意的事實就是簡單的算式會被捕獲爲調用,由於它們都是對算術運算符
(例如 + 和 * 等)的調用,而算術運算符本質就是內置函數。例如,咱們能夠對一個最簡單
的算式 1 + 1 調用 quote( ) 函數:
call3 <- quote(1 + 1)
call3
## 1 + 1
算式自己被保留,但它是一個調用,而且具備與調用徹底相同的結構:
is.call(call3)
## [1] TRUE
str(as.list(call3))
## List of 3
## $ : symbol +
## $ : num 1
## $ : num 1
掌握了前面全部關於捕獲表達式的用法後,咱們如今來捕獲一個嵌套調用,即包含更
多調用的調用:
call4 <- quote(sqrt(1 + x ^2))
call4
## sqrt(1 + x^2)
咱們可使用 pryr 擴展包中的一個函數查看調用的遞歸結構。先運行 install.
packages("pryr")安裝擴展包。以後,再調用 pryr::call_tree( ):
pryr::call_ _tree(call4)
## \- ()
## \- `sqrt
## \- ()
## \- `+
## \- 1
## \- ()
## \- `^
## \- `x
## \- 2
對於 call4,其遞歸結構以樹形結構輸出。\-( ) 運算符是指調用,`var 表明一個
符號對象 var,其餘部分是字面值。在前面的輸出結果中,咱們能夠看到符號和調用都被
捕獲,而且保留了字面值。
若是你對錶達式調用的樹形結構感到好奇,可使用函數 pryr::call_tree( )進
行查看,它精確地反映了 R 處理表達式的方式。
2.修改表達式
當表達式被捕獲爲一個調用對象時,能夠把它看成列表進行修改。例如,咱們能夠用
另外一個符號替換調用中的第 1 個元素來更改要調用的函數:
call1
## rnorm(5)
call1[[1]] <- quote(runif)
call1
## runif(5)
就這樣,rnorm(5) 被修改成 runif(5)。
也能夠在調用中添加新的參數:
call1[[3]] <- -1
names(call1)[[3]] <- "min"
call1
## runif(5, min = -1)
這個調用便有了另外一個參數:min = −1。
3.捕獲函數參數表達式
經過前面幾個示例,咱們學習瞭如何使用 quote( ) 函數捕獲一個已知的表達式,而
substitute( )能夠做用於任意的用戶輸入表達式。假設咱們想捕獲參數 x 的表達式。
首先想到的一種實現方法是用 quote( ):
fun1 <- function(x) {
quote(x)
}
在以 rnorm(5) 爲參數調用函數時,fun1( ) 能不能捕獲輸入表達式呢?咱們試驗一下:
fun1(rnorm(5))
## x
很顯然,quote(x) 只能捕獲 x ,而不是輸入表達式 rnorm(5)。爲了正確地捕獲它,
咱們須要使用 substitute( )。這個函數用於捕獲表達式,而且用捕獲到的表達式替換
現有符號。該函數最簡單的用法是捕獲函數參數的表達式:
fun2 <- function(x) {
substitute(x)
}
fun2(rnorm(5))
## rnorm(5)
經過這個實現,fun2( ) 返回輸入表達式而不是 x,由於 x 被輸入表達式(本例中的
rnorm(5))替換了。
如下兩個示例演示了用一個語言對象或字面值的列表做爲參數時,substitute( ) 的
運行方式。第 1 個示例中,咱們用 1 替換給定表達式中的符號 x:
substitute(x + y + x ^2, list(x = 1))
## 1 + y + 1^2
第 2 個示例中,咱們將做爲函數名的符號 f 替換爲另外一個被引用的函數名 sin:
substitute(f(x + f(y)), list(f = quote(sin)))
## sin(x + sin(y))
如今,咱們能夠用 quote( ) 捕獲某個表達式,用 substitute( ) 捕獲用戶輸入的
表達式。
4.建立函數調用
除了捕獲表達式,咱們還能夠直接用內置函數建立語言對象。例如,call1 就是
用 quote( ) 捕獲的:
call1 <- quote(rnorm(5, mean =3))
call1
## rnorm(5, mean = 3)
使用 call( ) 建立一個帶有相同參數的相同函數的調用:
call2 <- call("rnorm", 5, mean = 3)
call2
## rnorm(5, mean = 3)
或者,也能夠用 as.call( ) 函數將一個調用成分的列表轉換爲調用:
call3 <- as.call(list(quote(rnorm), 5, mean = 3))
call3
## rnorm(5, mean = 3)
以上 3 種方法建立的調用徹底相同,即它們調用的函數名稱和參數都同樣,咱們能夠
調用 identical( ) 來證明:
identical(call1, call2)
## [1] TRUE
identical(call2, call3)
## [1] TRUE函數