【问题标题】:Define S4 class inheriting from function定义从函数继承的 S4 类
【发布时间】:2022-01-12 20:27:25
【问题描述】:

我正在尝试编写一个专门返回与输入长度相同的数字向量的 S4 类。我想我很接近了;我现在遇到的问题是我只能从 GlobalEnv 中的函数创建新类。

library(S4Vectors)

setClass("TransFunc", contains = c("function"), prototype = function(x) x)

TransFunc <- function(x) {
  if (missing(x)) return(new("TransFunc"))
  new2("TransFunc", x)
}

.TransFunc.validity <- function(object) {
  msg <- NULL
  if (length(formals(object)) > 1) {
    msg <- c(msg, "TransFunc must only have one argument.")
  }
  res1 <- object(1:5)
  res2 <- object(1:6)
  if (length(res1) != 5 || length(res2) != 6) {
    msg <- c(msg, "TransFunc output length must equal input length.")
  }
  if (!class(res1) %in% c("numeric", "integer")) {
    msg <- c(msg, "TransFunc output must be numeric for numeric inputs.")
  }
  if (is.null(msg)) return(TRUE)
  msg
}

setValidity2(Class = "TransFunc", method = .TransFunc.validity)

mysqrt <- TransFunc(function(x) sqrt(x))
mysqrt <- TransFunc(sqrt) ## Errors... why??
## Error in initialize(value, ...) : 
##   'initialize' method returned an object of class “function” instead 
##   of the required class “TransFunc”

让类直接从函数继承的好处是能够将它们用作常规函数:

mysqrt(1:5)
## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 
body(mysqrt) <- expression(sqrt(x)^2)
mysqrt(1:10)
##  [1]  1  2  3  4  5  6  7  8  9 10

为什么在全局环境之外传递函数时会出错?

【问题讨论】:

    标签: r oop r-s4


    【解决方案1】:

    它不适用于 sqrt,因为 sqrt 是 primitive

    我不知道有任何函数只接受一个参数并且不是原始的。因此,我降低了您的有效性以演示您的代码如何与预加载包中的其他功能一起使用:

     #using your class definition and counstructor
     .TransFunc.validity <- function(object) {
       msg <- NULL
       res1 <- object(1:5)
       if (!class(res1) %in% c("numeric", "integer")) {
         msg <- c(msg, "TransFunc output must be numeric for numeric     inputs.")
       }
       if (is.null(msg)) return(TRUE)
       msg
      }  
    
      setValidity2(Class = "TransFunc", method = .TransFunc.validity)
    

    这是mean默认版本的结果

    mymean <- TransFunc(mean.default)
    mymean(1:5)
    [1] 3
    

    这是一个解决方法,通过修改 initialize 为您的类捕获原语并将它们转换为闭包:

    #I modified the class definition to use slots instead of prototype
    setClass("TransFunc", contains = c("function"))
    
    TransFunc <- function(x) {
    if (missing(x)) return(new("TransFunc"))
    new2("TransFunc", x)
    }
     
    # Keeping your validity I changed initilalize to:
    
     setMethod("initialize", "TransFunc",
          function(.Object, .Data = function(x) x , ...) {
              if(typeof(.Data) %in% c("builtin", "special"))
                        .Object <- callNextMethod(.Object, function(x) return(.Data(x)),...)
                  
              else 
                 .Object <- callNextMethod(.Object, .Data, ...)
                                                  
              
              .Object                                    
                                                  
          })     
    

    我得到了以下结果

    mysqrt <- TransFunc(sqrt)
    mysqrt(1:5)
    [1] 1.000000 1.414214 1.732051 2.000000    2.236068
    

    编辑:
    在 cmets @ekoam 为您的班级提出了一个更通用的 initilaize 版本:

    setMethod("initialize", "TransFunc", function(.Object, ...) 
     {maybe_transfunc <- callNextMethod();
          if (is.primitive(maybe_transfunc)) 
              .Object@.Data <- maybe_transfunc 
          else .Object <- maybe_transfunc; 
     .Object})  
    

    编辑 2:

    @ekoam 提供的方法不维护新类。例如:

    mysqrt <- TransFunc(sqrt)
    mysqrt
    # An object of class "TransFunc"
    # function (x)  .Primitive("sqrt")
    mysqrt
    # function (x)  .Primitive("sqrt")
    

    第一个提议的方法确实有效并维护了新类。正如 cmets 中所讨论的,另一种方法是在构造函数期间捕获原语,而不是创建自定义的初始化方法:

    library(pryr)
    TransFunc <- function(x) {
      if (missing(x)) return(new("TransFunc"))
      if (is.primitive(x)) {
        f <- function(y) x(y)
        # This line isn't strictly necessary, but the actual call
        # will be obscured and printed as 'x(y)' requiring something
        # like pryr::unenclose() to understand the behavior. 
        f <- make_function(formals(f), substitute_q(body(f), environment(f)))
      } else {
        f <- x
      }
      new2("TransFunc", f)
    }
    

    【讨论】:

    • 很好的解决方案。我想我们可以通过callNextMethod在if条件之前进一步概括,先使用默认方法,然后测试返回值是否原始。这样,我们就不必手动构造一个新的匿名函数了。像这样的东西:setMethod("initialize", "TransFunc", function(.Object, ...) {maybe_transfunc &lt;- callNextMethod(); if (is.primitive(maybe_transfunc)) .Object@.Data &lt;- maybe_transfunc else .Object &lt;- maybe_transfunc; .Object})
    • 使用我评论中的代码和 OP 的原始 setClass(...)TransFunc,我运行 TransFunc(sqrt) 甚至 TransFunc(substitute) 并收到预期的输出。我认为只要initialize方法没有到达返回步骤,就不会抛出错误。也许在您的计算机上尝试上面的代码? @GradaGukovic
    • @dayne 自从写下我的回复后,我意识到从防御性编程的角度来看,最好的解决方案是最有可能让 initialize 单独离开并在 costructor 中捕获原始函数,即 TransFunc &lt;- function(x) { if (missing(x)) return(new("TransFunc")); if(is.primitive(x)) x &lt;- function(y) return(x(y)); new2("TransFunc", x); } 但我我非常忙,我现在没有时间测试这个。
    • 该方法有效,尽管有一个额外的步骤,因此不会隐藏行为。对一种方法与另一种方法的优势有何想法?
    • @dayne 我认为修改构造函数更好,因为它处理问题,它出现在哪里而不是它导致问题的位置。此外,我不太喜欢搞乱initialize() 之类的低级功能。这使得代码更难阅读和维护。我从traceback() 开始而不是从类的定义开始提出了最初的解决方案。
    猜你喜欢
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多