第六轮回 全局配置
异教徒被囚禁于第六轮回的燃烧坟墓中.
一个全局配置可以通过”<<-“实现:
> 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努力去仅仅复制那些必须使用的对象,比如说当这个对象在函数里边发生变化的时候.这个轮回是大致概念上的正确,而非字里行间的正确.