在《数据分析软件:使用 R 编程》一书中,约翰·钱伯斯强调,通常不应为函数的副作用而编写函数;相反,一个函数应该在不修改其调用环境中的任何变量的情况下返回一个值。相反,使用 data.table 对象编写好的脚本应该特别避免使用对象赋值 with <-
,通常用于存储函数的结果。
首先,是一个技术问题。想象一个被调用的 R 函数proc1
,它接受一个data.table
对象x
作为其参数(可能还有其他参数)。proc1
返回 NULL 但x
使用:=
. 据我了解,proc1
调用只是因为承诺的工作方式而proc1(x=x1)
复制。x1
但是,如下所示,原始对象x1
仍被 修改proc1
。为什么/这是怎么回事?
> require(data.table)
> x1 <- CJ(1:2, 2:3)
> x1
V1 V2
1: 1 2
2: 1 3
3: 2 2
4: 2 3
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> proc1(x1)
NULL
> x1
V1 V2 y
1: 1 2 2
2: 1 3 3
3: 2 2 4
4: 2 3 6
>
此外,似乎 usingproc1(x=x1)
并不比直接在 x 上执行该过程慢,这表明我对 Promise 的模糊理解是错误的,并且它们以传递引用的方式工作:
> x1 <- CJ(1:2000, 1:500)
> x1[, paste0("V",3:300) := rnorm(1:nrow(x1))]
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> system.time(proc1(x1))
user system elapsed
0.00 0.02 0.02
> x1 <- CJ(1:2000, 1:500)
> system.time(x1[,y:= V1*V2])
user system elapsed
0.03 0.00 0.03
因此,鉴于将 data.table 参数传递给函数不会增加时间,这使得为 data.table 对象编写过程成为可能,同时结合了 data.table 的速度和函数的通用性。但是,鉴于 John Chambers 所说,函数不应该有副作用,在 R 中编写这种类型的程序编程真的“可以”吗?为什么他认为副作用是“坏的”?如果我要忽略他的建议,我应该注意哪些陷阱?我能做些什么来编写“好”的 data.table 程序?