S4 class: 在不返回对象的情况下在另一个方法中更新槽 (R)

S4 class: update slot in another method without returning the object (R)

对于我正在处理的 R 包(它创建了一个 S4 class),我想延迟加载一些数据,直到用户实际要求它(因为它可能需要也可能不需要并且加载这需要一点时间)。这将要求我在其 getter (又名访问器)方法中设置一个插槽的值,如果它以前没有被加载的话。但是我无法获得 "stick" 的新值。这是一个 MRE:

setClass("A", representation(value = "numeric"))

setGeneric("value<-", function(x, value) standardGeneric("value<-"))
setGeneric("value", function(x) standardGeneric("value"))

setMethod("value<-", signature("A", "numeric"),
          function(x, value) 
          {
            x@value = value
            x
          })    

setMethod("value", signature(x = "A"),
          function(x) 
          {
            if(!length(x@value))
              value(x) <- 20
            x@value
          })    

这会产生以下结果:

> a <- new("A")
> value(a)
[1] 20
> a
An object of class "A"
Slot "value":
numeric(0)

因此 value() 函数 return 提供了所需的新值 (20),但该值实际上并未在对象中更新。在 getter 中执行 x@value <- value 而不是 value(x) <- 20 也没有成功。

看来问题是我没有 return 在我的 getter 中更新对象(我的 setter 的方式),但我还有其他事情要 return 在我的 getter (值)中。

什么是正确的做法™?

谢谢!

编辑:

在进一步研究 S4 传值语义后,我得出的结论是这是 Simply Not Possible™。如果插槽已更新,则必须 return 编辑对象,而不能 return 其他内容。有人可以确认我的结论是正确的吗?

@Alexis 的评论提到了 R6 类。当我被要求为我当前的项目(针对 BioConductor)进行 S4 类 时,R6 ReferenceClasses 文档中的以下句子引起了我的注意:'Reference classes are implemented as S4 classes with a data part of type "environment".'

因此,如果我在使用 S4 类 时真的想要可变元素,我可以按如下方式模拟 R6:

setClass("A", representation(inMutable = "ANY", 
                             .env = "environment"))

A = function(inMutable = NULL, mutable = NULL) {
  x <- new("A", 
           inMutable = inMutable,
           .env = new.env())
  x@.env$mutable  = mutable
  x@inMutable <- inMutable
  x
}

setGeneric("inMutable<-", function(x, value) standardGeneric("inMutable<-"))
setGeneric("inMutable", function(x) standardGeneric("inMutable"))
setGeneric("mutable<-", function(x, value) standardGeneric("mutable<-"))
setGeneric("mutable", function(x) standardGeneric("mutable"))

#setter
setMethod("inMutable<-", signature("A", "numeric"),
          function(x, value) 
          {
            x@inMutable <- value
            x
          })    

#getter
setMethod("inMutable", signature("A"),
          function(x) 
          {
            if (!length(x@inMutable))
              message("Hmmm, you haven't initialized a value for 'inMutable'.",
                      " I'm afraid I can't do anything about that now.")
            x@inMutable
          })    

#setter
setMethod("mutable<-", signature("A", "numeric"),
          function(x, value) 
          {
            x@.env$mutable <- value
            x
          })    

#getter (mutable!)
setMethod("mutable", signature("A"),
          function(x) 
          {
            if (!length(x@.env$mutable)) {
              message("Ah. You haven't initialized a value for 'mutable'. ",
                      "Lucky for you I can initialize it for you now.")
              x@.env$mutable = 12
            }
            x@.env$mutable
          })    

那我可以这样做:

> a <- A()
> mutable(a)
Ah. You haven't initialized a value for 'mutable'. 
Lucky for you I can initialize it for you now.
[1] 12
> mutable(a)
[1] 12
>
> inMutable(a)
Hmmm, you haven't initialized a value for 'inMutable'. 
I'm afraid I can't do anything about that now.
NULL
> inMutable(a) <- 18
> inMutable(a)
[1] 18
> 

显然,参考文献 类 和 R6 提供了更丰富、更强大的解决方案,但在紧要关头,这似乎是一个可行的 S4 选项。当然,我还没有在野外彻底测试它,看看它可能在哪里崩溃。