需要全局分配的函数作为在 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 取决于在某些父环境中找到 abc 的正确值。我更改了定义,使它们成为显式参数

  • updatedef 没有 return 任何东西,它只是替换了 def 的值最近的父环境。我明确列出了这些值 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 bc 的依赖从它的用法中不清楚,这可能会使不熟悉代码的人感到困惑(甚至你可能在一两年后回头看!)。