定义从函数继承的S4类。

3

我正在尝试编写一个 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”

从function直接继承类的好处是能够像常规函数一样使用它们。
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

为什么在传递函数到全局环境之外时会出现错误?
1个回答

3

对于sqrt的操作无效,因为sqrt是一个原始的函数。

我不知道有任何只接受一个参数并且不是原始的函数。因此,我缩小了您的可行性范围,以演示您的代码如何与预装软件包中的其他函数一起使用:

 #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

编辑:
在评论中,@ekoam提出了一个更通用的类初始化版本:

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")

第一种提出的方法确实有效,并且保持了新类。正如评论中所讨论的,另一种方法是在构造函数期间捕获原语类型,而不是创建自定义初始化方法:
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)
}

1
很棒的解决方案。我认为我们可以通过在if条件之前使用callNextMethod进一步概括它,以先使用默认方法,然后测试返回值是否为原始值。这样,我们就不必手动构造一个新的匿名函数。像这样:setMethod("initialize", "TransFunc", function(.Object, ...) {maybe_transfunc <- callNextMethod(); if (is.primitive(maybe_transfunc)) .Object@.Data <- maybe_transfunc else .Object <- maybe_transfunc; .Object}) - ekoam
1
通过我的评论中的代码和 OP 的原始 setClass(...)TransFunc,我运行了 TransFunc(sqrt) 甚至是 TransFunc(substitute) 并收到了预期的输出。我认为只要 initialize 方法没有达到返回步骤,它就不会抛出错误。也许你可以在你的电脑上尝试一下上面的代码?@GradaGukovic - ekoam
1
@dayne 自从写完我的回答后,我意识到从防御性编程的角度来看,最好的解决方案很可能是保持initialize不变,并在构造函数中捕获原始函数,即```TransFunc <- function(x) { if (missing(x)) return(new("TransFunc")); if(is.primitive(x)) x <- function(y) return(x(y)); new2("TransFunc", x); }但我非常忙,现在没有时间测试这个方法。 - Grada Gukovic
1
这种方法是可行的,尽管需要额外的步骤来避免行为被隐藏。您对两种方法的优劣有何想法? - dayne
1
@dayne 我认为修改构造函数更好,因为它处理问题出现的地方而不是引起问题的地方。此外,我不太喜欢搞乱低级功能,比如initialize()。这会使代码更难阅读和维护。我通过从traceback()开始向上工作而不是从类定义向下工作来想出最初的解决方案。 - Grada Gukovic
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接