了解自定义就地修改功能的代码?

Big*_*ist 13 r data.table

我遇到了这篇文章:来自Matt Dowle的http://r.789695.n4.nabble.com/speeding-up-perception-tp3640920p3646694.html,早些时候讨论过一些问题?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 
Run Code Online (Sandbox Code Playgroud)

具体来说,我在看:

"[<-.newclass" = function(x,i,j,value) x
Run Code Online (Sandbox Code Playgroud)

我试图了解那里做了什么以及如何使用这种表示法.

它看起来像我:

  • 我是行索引
  • j是列索引
  • value是要分配的值
  • x是正在考虑的对象

因此,我最好的猜测是我为现场修改定义了一个自定义函数(对于给定的类).

[<-.newclass 是类newclass的类修改.

了解发生的情况:通常以下代码应返回错误:

x = list(a = 1:10000, b = 1:10000) 
x[1, 2] = 42L 
Run Code Online (Sandbox Code Playgroud)

所以我想示例代码没有任何实际用途.

尝试使用逻辑:

一个简单的无意义尝试就是将要插入的值平方:

x[i, j] <- value^2
Run Code Online (Sandbox Code Playgroud)

全面尝试:

> 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
Run Code Online (Sandbox Code Playgroud)

这似乎不起作用.

我的问题:

"[<-.newclass" = function(x,i,j,value) x 
Run Code Online (Sandbox Code Playgroud)

这种符号究竟是如何工作的,我将如何使用它?

(我添加data.table标签,因为链接的讨论是关于data.table中的"by-reference"到位修改,我认为).

Mik*_*ila 18

`[<-`()功能(传统上)用于子分配,更广泛地说,是一种替换功能.它也是通用的(更具体地说,是一个内部泛型),它允许您为它编写自定义方法,正如您正确推测的那样.

更换功能

通常,当您调用替换函数时,例如...

foo(x) <- bar(y)
Run Code Online (Sandbox Code Playgroud)

... <-(右边)的表达式(bar(y)作为第一个value参数)作为命名参数传递给`foo<-`()with x,并且x用结果重新分配对象:也就是说,所述调用等同于写入:

x <- `foo<-`(x, value = bar(y))
Run Code Online (Sandbox Code Playgroud)

因此,为了工作,所有替换函数必须至少有两个参数,其中一个必须命名value.大多数替换函数只有这两个参数,但也有例外:例如`attr<-`,通常是子分配.

Subassignment

当你有一个subassignment呼叫像x[i, j] <- y,ij作为额外的参数给获得通过`[<-`()使用功能xy作为第一个和value参数,分别为:

x <- `[<-`(x, i, j, value = y) # x[i, j] <- y
Run Code Online (Sandbox Code Playgroud)

在的情况下matrixdata.frame,i并且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"
Run Code Online (Sandbox Code Playgroud)

这有用还是合理?可能不是.但它是有效的R代码吗?绝对!

在实际应用程序中查看自定义子分配方法的情况并不常见,因为`[<-`()通常"正常工作",正如您所期望的那样,基于类的基础对象.一个值得注意的例外是`[<-.data.frame`,底层对象是一个列表,但子分配的行为类似于矩阵.(另一方面,许多类确实需要自定义子设置方法,因为默认`[`()方法会删除大多数属性,包括class属性,请参阅?`[`详细信息).


至于为什么你的例子不起作用:记住你正在编写一个泛型函数的方法,并且所有常规规则都适用.如果我们`[<-`()在您的示例中使用函数形式并展开方法调度,我们可以立即看到它失败的原因:

`[<-.newclass` <- function(x, i, j, value) {
  x <- `[<-.newclass`(x, i, j, value = value^2)  # x[i, j] <- value^2
}
Run Code Online (Sandbox Code Playgroud)

也就是说,函数是递归定义的,没有基本情况,导致无限循环.解决这个问题的一种方法是unclass(x)在调用下一个方法之前:

`[<-.newclass` <- function(x, i, j, value) {
  x <- unclass(x)
  x[i, j] <- value^2
  x # typically you would also add the class back here
}
Run Code Online (Sandbox Code Playgroud)

(或者,使用一种更高级的技术,也可以使用如下明确的下一个方法替换正文:NextMethod(value = value^2).这对继承和超类来说更好.)

只是为了验证它是否有效:

x <- matrix(1:9, 3, 3)
class(x) <- "newclass" 

x[1, 2] <- 9
x
#>      [,1] [,2] [,3]
#> [1,]    1   81    7
#> [2,]    2    5    8
#> [3,]    3    6    9
Run Code Online (Sandbox Code Playgroud)

完全令人困惑!


至于Dowle的"无所事事"子分配示例的背景,我相信这是为了说明在R 2.13.0中,自定义子分配方法总是会导致对象的深层副本,即使方法本身也是如此什么都没有.(这不再是这种情况,因为我相信R 3.1.0.)

reprex包(v0.2.0)创建于2018-08-15 .