R 的函數也可以像一般的變數一樣,當作參數傳入其他的函數中使用,或是作為函數的傳回值。最簡單的例子就是 do.call
函數,它提供了另外一種函數的呼叫方式,可以讓使用者將參數以列表變數的方式傳入:
do.call(hypotenuse, list(x = 3, y = 4))
[1] 5
這樣的效果相當於直接呼叫:
hypotenuse(x = 3, y = 4)
在將函數當作參數使用時,不一定要先建立具名的函數,假設我們自訂一個函數為 my.plus
,將其傳入 do.call
中使用:
my.plus <- function(x, y) x + y do.call(my.plus, list(1, 2))
[1] 3
我們可以改用匿名函數的方式,讓程式碼更簡潔:
do.call(function(x, y) x + y, list(1, 2))
[1] 3
將函數作為傳回值的狀況比較少見,ecdf
這個計算 empirical cumulative distribution function 的函數是一個比較有可能會遇到的例子:
x <- rnorm(50) x.ecdf <- ecdf(x) is.function(x.ecdf)
[1] TRUE
x.ecdf(-1.3)
[1] 0.02
變數範圍(variable scope)是指一個變數的存在範圍,亦即變數可以被使用的區域。
R 在取用變數時,會依循環境空間的繼承性原則,先從目前的環境空間中尋找變數,若目前的環境空間中沒有要找的變數,就會循著母環境空間持續往上尋找,直到找到符合的變數為止。
依據 R 環境空間的繼承性原則,在函數中我們可以直接取用外部全域的變數:
x.out <- 8 my.func <- function() { message("x.out is ", x.out) } my.func()
x.out is 8
當我們在 my.func
函數中取用 x.out
這個變數時,R 會先在 my.func
的環境空間中尋找 x.out
這個變數,當發現這個變數不存在時,就會繼續從它的母環境空間中尋找,也就是全域環境空間(global environment),最後取得在全域環境空間中所定義的 x.out
。
全域環境空間中所定義的變數,在任何地方都可以被使用,所以定義在全域環境空間中的變數也稱為全域變數(global variables),而定義在一般函數中的變數,則稱為區域變數(local variables)。
若在一個函數中建立一個變數後,可以在函數的內部使用該變數,但在此函數的外部就無法使用:
my.func <- function() { x.in.func <- 12 message("x.in.func is ", x.in.func) } my.func()
x.in.func is 12
如果在函數外部就無法存取 x.in.func
這個函數內部的變數:
message("x.in.func is ", x.in.func)
Error in message("x.in.func is ", x.in.func) : 找不到物件 'x.in.func'
如果在一個函數中又建立了一個子函數,則在子函數中可以直接取用上層函數中的變數:
f <- function(x) { y <- 1 g <- function(x) { (x + y) / 2 } g(x) } f(5)
[1] 3
若我們將 g
函數移到 f
函數之外,這樣的話 g
函數就不是 f
的子函數,也就無法取得 f
函數內部的變數:
f <- function(x) { y <- 1 g(x) } g <- function(x) { (x + y) / 2 } f(5)
Error in g(x) : 找不到物件 'y'
環境空間的繼承性原則可以讓 R 的程式開發者更容易撰寫程式,不過也時常會帶來一些困擾,讓程式碼變得難以維護,假設有一個 h
函數定義如下:
h <- function(x) { x + y }
乍看之下,這個 h
函數中的 y
變數沒有定義,應該無法執行,若在一個乾淨的 R 執行環境之下,會產生找不到 y
變數的錯誤:
h(3)
Error in h(3) : 找不到物件 'y'
但是如果我們在全域環境空間中建立一個 y
變數,狀況就會改觀了:
y <- 1:3 h(3)
[1] 4 5 6
原本應該會產生錯誤的函數,現在變成可以正常執行了,這是因為 R 在執行 h
函數時,當發現 h
函數中無法找到 y
這個變數的定義時,就會循著 h
函數上層的環境空間來尋找,由於我們在全域環境空間中建立了一個 y
變數,所以 R 就會直接取用這個 y
全域變數。
當開發大型程式時,要小心使用全域變數,否則很容易讓程式產生很多 bugs,而且也非常難維護。我們來看下面這個例子:
h2 <- function(x) { if (runif(1) > 0.5) y <- 2 x * y }
在這個例子,y
有一半的機率會在 h2
被定義,如果 h2
函數中有定義 y
,它就會使用 h2
自己定義的 y
,若 h2
函數中沒有定義自己的 y
變數,就會使用全域的 y
變數,相信大家都可以看得出來,這樣的程式碼架構是很容易出現 bugs 的。
通常在撰寫 R 程式時,應該盡可能將所有需要使用到的變數都透過參數來傳遞,不要使用全域變數的方式來取得,以降低程式出錯的機率與維護的成本。