第六輪迴 全局配置
異教徒被囚禁於第六輪迴的燃燒墳墓中.
一個全局配置可以通過”<<-“實現:
> x <- 1
> y <- 2
> fun
function ()
{
x <- 101
y <<- 102
}
> fun()
> x
[1] 1
> y
[1] 102
但這就像是在火山口生活一樣.
如果你需要使用“<<-“,請三思.如果你深思熟慮之後還是覺得需要使用“<<-“,請再三思.只有當你的老闆因爲你的不作爲而生氣的紅了臉的時候,你也應該只是暫時性的使用“<<-“.早已經有提議(並非半開玩笑的)從R中消除“<<-“,但這並不意味着就清除了全局配置,只是強迫你使用assign()去實現相同的目的.
全局配置到底哪裏錯了呢? Surprise.
Surprise在電影和小說中是美好的.但是在程序中就不那麼討人喜歡了.
在R中,除了少數幾個函數明確的有負作用(即對函數之外的環境造成影響)之外,其餘的函數我們都期望是沒有副作用的.函數中如果使用了全局配置,那就是與這種期望公然對抗.對於不熟悉這些代碼的用戶(甚至是幾周前編寫這些代碼的作者),這裏將會有一個變量在魔術般地變化.
全局配置的一個有用的(並不是極壞的)特殊場合是記憶.具體講就是:程序的運行結果可以被存儲起來,當後期再有相同的計算的時候,這個結果就可以僅僅從存儲中找到而不是再次計算.全局配置在這種場合並不會令人擔心,因爲這個對用戶並不可見.這也有一個命名銷燬的問題–如果你在兩個不同的函數中採用相同名字的變量去記憶結果,銷燬就隨之而來.
在R中,我們可以通過一個局部全局變量來實現記憶.(“局部全局”挺起來有點幽默,但是它卻簡潔地描述了到底是怎麼一回事.)在下面計算斐波那契數列的例子中,我們使用了“<<-“,並且安全地使用.
fibonacci <- local({
memo <- c(1, 1, rep(NA, 100))
f <- function(x)
{
if(x == 0) return(0)
if(x < 0) return(NA)
if(x > length(memo))
stop("'x' too big for implementation")
if(!is.na(memo[x])) return(memo[x])
ans <- f(x-2) + f(x-1)
memo[x] <<- ans
ans
}
})
糊塗神是怎麼說的?我們有一個僅僅通過“<<-“操作的本地化方式來實現記憶的函數.但是我們再該函數的本地環境中存放備忘記錄.返回值就是大括號最後的那個值.當我們定義函數的時候一般不會對返回對象命名,但是在這種情況下我們需要命名因爲這個函數需要遞歸調用.
現在我們玩玩看:
> fibonacci(4)
[1] 3
> head(get(’memo’, envir=environment(fibonacci)))
[1] 1 1 2 3 NA NA
通過計算入參是4的Fibonacci(),memo的第三個和第四個函數被填充進來.這些值將不會被重複計算,需要用的時候僅僅是查表就可以了.
R總是進行着值傳遞.它從不會進行引用傳遞.
在R中有兩種人:一種是理解上邊所我所說的,一種不是.
如果你不理解這些的話,R是很適合你的–意思是R對你來說是安全的(儘管本章都在說一些相反的話題).f翻譯成人話就是說在R中讓數據”腐爛”是極度困難的.畢竟智者知無止境.
如果你真的知道這一輪迴我們所討論的問題,那麼你或許早就領悟到了R是深受函數式編程影響的–負作用被降到最低.或許你會擔心這預示着內存的嚴重低效使用.好吧,這輪迴我們討論的問題就是一個謊言.如果這只是字面上的正確,當對象(或許非常大)是函數入參時總會被複制.事實上,R努力去僅僅複製那些必須使用的對象,比如說當這個對象在函數裏邊發生變化的時候.這個輪迴是大致概念上的正確,而非字裏行間的正確.