需要全局分配的函数作为在 R 中重用代码的一种方式 - 好的还是坏的做法?
Functions that require global assignment as a way of reusing code in R - good or bad practice?
所以我是 运行 R 中的一个模拟,每次发生某个事件时,我都需要重新计算一些变量,而这个事件可能会在一个循环的单次迭代中发生多次。代码看起来像这样。
nums = a+b*c;
nums[v:(v+3)] = 1
listicle = lapply(stuff, func)
n = sum(nums)-t
看起来很明智,为了不重复一遍,我把它包装在一些函数中,所以它看起来像...
updateVars = function(){
nums <<- a+b*c;
nums[v:(v+3)] <<- 1
listicle <<- lapply(stuff, func)
n <<- sum(nums)-t
}
然后每当我需要更新这些变量时,我只需调用 updateVars()
.
不过,我正在阅读 R inferno,在第 6 章中,他们认为全局分配是一种不好的做法。
不过,我不确定如果没有这些函数,我将如何保持代码的简短和可读性。是否有我遗漏的技巧,或者这是使用全局赋值的有效例外?
谢谢,
内森
编辑:可重现的代码。
a=rnorm(100,3,2)
b=rnorm(100,1,3)
c=rnorm(100,4,2)
updatedef = function()
{
d<<-a+b-c^2
e<<-var(a^b)-mean(c)
f<<-lapply(a+b+c, function(t){rnorm(10,t,t/3)})
}
for (i in 1:400){
a=a+1
updatedef()
print(d+e)
print(f)
b=2*b
updatedef()
print(d+e)
print(f)
c=runif(100)
print(d+e)
print(f)
}
我发现您的示例有两个 "global variable" 问题,我重写了它以避免这两个问题。它并没有真正让代码变长,它看起来更容易阅读(尤其是对于不熟悉代码的人来说)并且对我来说绝对感觉 更安全。
我看到的问题是:
updatedef
取决于在某些父环境中找到 a
、b
和 c
的正确值。我更改了定义,使它们成为显式参数
updatedef
没有 return
任何东西,它只是替换了 d
、e
和 f
的值最近的父环境。我明确列出了这些值 return.
代码如下:
update_def = function(a, b, c) {
d = a + b - c^2
e = var(a^b) - mean(c)
f = lapply(a + b + c, function(t) rnorm(10, t, t / 3))
return(list(d = d, e = e, f = f))
}
for (i in 1:4) {
a = a + 1
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
b = 2 * b
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
c = runif(100)
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
}
泛化
我知道这是一个玩具示例 - 也许您的真实代码比 a, b, c
需要计算的变量更多。在这种情况下,就像我创建的 def
列表一样,我建议您使用 list
来存储它们。您甚至可能想创建自己的 class 扩展列表有一些方法。
在上面的示例中,我们可以使用自定义 print
方法创建 def
class,该方法执行代码中经常重复的两行代码:
update_def_c = function(a, b, c) {
d = a + b - c^2
e = var(a^b) - mean(c)
f = lapply(a + b + c, function(t) rnorm(10, t, t / 3))
def = list(d = d, e = e, f = f)
class(def) = c("def", "list")
return(def)
}
print.def = function(def, ...) {
print(def$d + def$e, ...)
print(def$f, ...)
}
然后,我们可以直接调用 print(def)
,而不是重复 print(d+e); print(f)
。最重要的是,如果稍后我们希望更改打印行为,只需更改一处即可。这使您的代码更加模块化。
我认为这些更改使您的代码对于普通 R 用户而言更具可读性。假设始终是函数 不会 有副作用(比如在没有显式赋值的情况下更改值)。你的 updatedef
是一个 well-named 函数,因为它的名字暗示了它的作用,但它对 a
b
和 c
的依赖从它的用法中不清楚,这可能会使不熟悉代码的人感到困惑(甚至你可能在一两年后回头看!)。
所以我是 运行 R 中的一个模拟,每次发生某个事件时,我都需要重新计算一些变量,而这个事件可能会在一个循环的单次迭代中发生多次。代码看起来像这样。
nums = a+b*c;
nums[v:(v+3)] = 1
listicle = lapply(stuff, func)
n = sum(nums)-t
看起来很明智,为了不重复一遍,我把它包装在一些函数中,所以它看起来像...
updateVars = function(){
nums <<- a+b*c;
nums[v:(v+3)] <<- 1
listicle <<- lapply(stuff, func)
n <<- sum(nums)-t
}
然后每当我需要更新这些变量时,我只需调用 updateVars()
.
不过,我正在阅读 R inferno,在第 6 章中,他们认为全局分配是一种不好的做法。
不过,我不确定如果没有这些函数,我将如何保持代码的简短和可读性。是否有我遗漏的技巧,或者这是使用全局赋值的有效例外?
谢谢, 内森
编辑:可重现的代码。
a=rnorm(100,3,2)
b=rnorm(100,1,3)
c=rnorm(100,4,2)
updatedef = function()
{
d<<-a+b-c^2
e<<-var(a^b)-mean(c)
f<<-lapply(a+b+c, function(t){rnorm(10,t,t/3)})
}
for (i in 1:400){
a=a+1
updatedef()
print(d+e)
print(f)
b=2*b
updatedef()
print(d+e)
print(f)
c=runif(100)
print(d+e)
print(f)
}
我发现您的示例有两个 "global variable" 问题,我重写了它以避免这两个问题。它并没有真正让代码变长,它看起来更容易阅读(尤其是对于不熟悉代码的人来说)并且对我来说绝对感觉 更安全。
我看到的问题是:
updatedef
取决于在某些父环境中找到a
、b
和c
的正确值。我更改了定义,使它们成为显式参数updatedef
没有return
任何东西,它只是替换了d
、e
和f
的值最近的父环境。我明确列出了这些值 return.
代码如下:
update_def = function(a, b, c) {
d = a + b - c^2
e = var(a^b) - mean(c)
f = lapply(a + b + c, function(t) rnorm(10, t, t / 3))
return(list(d = d, e = e, f = f))
}
for (i in 1:4) {
a = a + 1
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
b = 2 * b
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
c = runif(100)
def = update_def(a, b, c)
with(def, print(d + e))
print(def$f)
}
泛化
我知道这是一个玩具示例 - 也许您的真实代码比 a, b, c
需要计算的变量更多。在这种情况下,就像我创建的 def
列表一样,我建议您使用 list
来存储它们。您甚至可能想创建自己的 class 扩展列表有一些方法。
在上面的示例中,我们可以使用自定义 print
方法创建 def
class,该方法执行代码中经常重复的两行代码:
update_def_c = function(a, b, c) {
d = a + b - c^2
e = var(a^b) - mean(c)
f = lapply(a + b + c, function(t) rnorm(10, t, t / 3))
def = list(d = d, e = e, f = f)
class(def) = c("def", "list")
return(def)
}
print.def = function(def, ...) {
print(def$d + def$e, ...)
print(def$f, ...)
}
然后,我们可以直接调用 print(def)
,而不是重复 print(d+e); print(f)
。最重要的是,如果稍后我们希望更改打印行为,只需更改一处即可。这使您的代码更加模块化。
我认为这些更改使您的代码对于普通 R 用户而言更具可读性。假设始终是函数 不会 有副作用(比如在没有显式赋值的情况下更改值)。你的 updatedef
是一个 well-named 函数,因为它的名字暗示了它的作用,但它对 a
b
和 c
的依赖从它的用法中不清楚,这可能会使不熟悉代码的人感到困惑(甚至你可能在一两年后回头看!)。