我碰到了这篇帖子:http://r.789695.n4.nabble.com/speeding-up-perception-tp3640920p3646694.html,来自Matt Dowle,讨论得还早? data.table
包的实现思路。
他使用以下代码:
x = list(a = 1:10000, b = 1:10000)
class(x) = "newclass"
"[<-.newclass" = function(x,i,j,value) x # i.e. do nothing
tracemem(x)
x[1, 2] = 42L
具体地说,我正在查看:
"[<-.newclass" = function(x,i,j,value) x
我试图了解在此所做的工作以及如何使用此表示法。
在我看来,是这样:
因此,我最好的猜测是我为就位修改(对于给定的类)定义了一个自定义函数。
[<-.newclass
在类newclass的类修改中。
了解发生了什么: 通常,以下代码应返回错误:
x = list(a = 1:10000, b = 1:10000)
x[1, 2] = 42L
所以我想示例代码没有任何实际用途。
尝试使用以下逻辑:
一个简单的无意义的尝试是将要插入的值平方:
x[i, j] <- value^2
完全尝试:
> x = matrix(1:9, 3, 3)
> class(x) = "newclass"
> "[<-.newclass" = function(x, i, j, value) x[i, j] <- value^2 # i.e. do something
> x[1, 2] = 9
Error: C stack usage 19923536 is too close to the limit
这似乎不起作用。
我的问题
"[<-.newclass" = function(x,i,j,value) x
此符号的工作原理以及我将如何使用它?
(我想添加data.table标记,因为链接的讨论是关于data.table中“按引用”的就地修改)。
答案 0 :(得分:18)
`[<-`()
函数(传统上)用于subassignment,并且更广泛地是replacement function的一种。它也是通用的(更具体地说,是internal generic),它允许您正确猜想后write custom methods。
通常,当您调用替换功能时,例如...
foo(x) <- bar(y)
... <-
右侧的表达式(所以这里是bar(y)
)被作为命名为value
的参数传递给`foo<-`()
,{{1} }作为第一个参数,并向对象x
重新分配结果:也就是说,上述调用等效于编写:
x
因此,为了完全起作用,所有替换函数必须至少包含两个参数,其中之一必须命名为x <- `foo<-`(x, value = bar(y))
。
大多数替换函数只有两个自变量,但也有例外:例如`attr<-`
,通常是 subassignment 。
当您进行诸如value
之类的子分配调用时,x[i, j] <- y
和i
作为附加参数通过j
和{{1 }}分别作为第一个参数和`[<-`()
:
x
对于y
或value
,x <- `[<-`(x, i, j, value = y) # x[i, j] <- y
和matrix
将用于选择行和列;但通常情况并非必须如此。自定义类的方法可以对参数执行任何操作。考虑以下示例:
data.frame
这有用还是合理? 可能不是。但是它是有效的R代码吗? 绝对!
在实际的应用程序中看到自定义子分配方法的情况较少见,因为i
通常基于类的基础对象,可以按您期望的那样“正常工作”。 `[<-.data.frame`
是一个值得注意的例外,其中基础对象是列表,但是子分配的行为类似于矩阵。 (另一方面,许多类 do 需要一个自定义sub setting 方法,因为默认的j
方法会删除大多数属性,包括x <- matrix(1:9, 3, 3)
class(x) <- "newclass"
`[<-.newclass` <- function(x, y, z, value) {
x + (y - z) * value # absolute nonsense
}
x[1, 2] <- 9
x
#> [,1] [,2] [,3]
#> [1,] -8 -5 -2
#> [2,] -7 -4 -1
#> [3,] -6 -3 0
#> attr(,"class")
#> [1] "newclass"
属性,有关详细信息,请参见`[<-`()
。
关于您的示例为何不起作用的原因:请记住,您正在为通用函数编写方法,并且所有常规规则都适用。如果我们使用`[`()
的函数形式并扩展示例中的方法dispatch,我们可以立即看到失败的原因:
class
也就是说,该函数是在没有基本情况的情况下递归定义的,从而导致无限循环。解决此问题的一种方法是在调用下一个方法之前先进入?`[`
:
`[<-`()
(或者,使用某种更高级的技术,也可以将主体替换为诸如`[<-.newclass` <- function(x, i, j, value) {
x <- `[<-.newclass`(x, i, j, value = value^2) # x[i, j] <- value^2
}
的显式next方法。这种方法在继承和超类方面表现更好。)
只是为了验证它是否有效:
unclass(x)
完全混乱!
对于Dowle的“不做任何事情”子分配示例的上下文,我相信这是为了说明在R 2.13.0中,自定义子分配方法将始终导致制作对象的深层副本,即使方法本身什么也没做。 (我相信自R 3.1.0起,情况已不再如此。)
由reprex package(v0.2.0)于2018-08-15创建。