【问题标题】:Understanding code for custom in-place modification function?了解自定义就地修改功能的代码?
【发布时间】:2018-08-09 14:34:03
【问题描述】:

我看到了这个帖子:来自 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 

我具体看:

"[<-.newclass" = function(x,i,j,value) x

我试图了解那里做了什么以及如何使用这个符号。

在我看来是这样的:

  • i 是行索引
  • j 是列索引
  • value 是要赋值的值
  • x 是考虑中的对象

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

[&lt;-.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 中的“按引用”修改,我认为)。

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    `[&lt;-`() 函数(传统上)用于subassignment,更广泛地说,是replacement function 的一种类型。它也是通用的(更具体地说,internal generic),它允许您为它使用write custom methods,正如您所猜测的那样。

    替换函数

    一般来说,当你调用一个替换函数时,比如...

    foo(x) <- bar(y)
    

    ...&lt;- 右侧的表达式(所以这里是bar(y))作为命名value 参数传递给`foo&lt;-`()x 作为第一个参数,对象@将 987654335@ 重新赋值为结果:即所说的调用相当于写:

    x <- `foo<-`(x, value = bar(y))
    

    所以为了工作,所有替换函数必须至少有两个参数,其中一个必须命名为value。 大多数替换函数只有这两个参数,但也有例外:例如`attr&lt;-`,通常还有subassignment

    子赋值

    当您有像x[i, j] &lt;- y 这样的子赋值调用时,ij 作为附加参数传递给 `[&lt;-`() 函数,xy 分别作为第一个参数和 value 参数:

    x <- `[<-`(x, i, j, value = y) # x[i, j] <- y
    

    对于matrixdata.frameij 将用于选择行和列;但总的来说,情况并非如此。自定义类的方法可以对参数做任何事情。考虑这个例子:

    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"
    

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

    在实际应用程序中很少看到自定义子分配方法,因为 `[&lt;-`() 通常“正常工作”,正如您所期望的那样,基于您的类的底层对象。一个值得注意的例外是`[&lt;-.data.frame`,其中底层对象是一个列表,但子赋值的行为类似于矩阵。 (另一方面,许多类确实需要一个自定义的 subsetting 方法,因为默认的 `[`() 方法会删除大多数属性,包括 class 属性,请参阅 @ 987654356@了解详情)。


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

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

    也就是说,函数是递归定义的,没有基本情况,导致无限循环。解决这个问题的一种方法是在调用下一个方法之前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
    }
    

    (或者,使用更高级的技术,也可以将主体替换为显式的 next 方法,如下所示: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
    

    完全混乱!


    至于 Dowle 的“什么都不做”子赋值示例的上下文,我相信这是为了说明在 R 2.13.0 中,自定义子赋值方法总是会导致生成对象的深层副本,即使方法本身什么也没做。 (现在不再是这种情况了,因为我相信 R 3.1.0。)

    reprex package (v0.2.0) 于 2018 年 8 月 15 日创建。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-08
      • 2012-06-13
      相关资源
      最近更新 更多