2

我正在尝试编写一个专门返回与输入长度相同的数字向量的 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

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

4

1 回答 1

3

它不起作用,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

编辑:
@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")

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

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)
}
于 2022-01-15T02:47:10.840 回答