R:S3方法分派取决于参数

时间:2018-08-08 19:58:07

标签: r methods arguments dispatch

我有一个通用函数foo,我想根据提供给它的参数调用三种不同的方式。

foo <- function(...) UseMethod("foo")

#default
foo.default <- function(x, y, ...) {
#does some magic
print("this is the default method")
}

#formula
foo.formula <- function(formula, data = list(), ...) {
print("this is the formula method")
}

#data.frame
foo.data.frame <- function(data, x, y, ...) {
print("this is the data.frame method")
}

在下面,我将展示我如何期待调度方法工作,但是输出显示在每个调用下...

mydata <- data.frame(x=c(1,2,3,4),y=c(5,6,7,8))

#ways to call default function
foo(x = mydata$x, y = mydata$y)
#[1] "this is the default method"

#ways to call formula
foo(formula = mydata$x~mydata$y)
#[1] "this is the formula method"
foo(formula = x~y, data = mydata)
#[1] "this is the formula method"
foo(data = mydata, formula = x~y)  #ERROR
#[1] "this is the data.frame method"

#ways to call data.frame method
foo(data = mydata, x = x, y = y)
#[1] "this is the data.frame method"
foo(x = x, y = y, data = mydata) #ERROR
#Error in foo(x = x, y = y, data = mydata) : object 'x' not found

据我所知,所使用的方法取决于第一个参数的类。本质上,我希望将方法分派给取决于传递给通用函数foo的参数,而不是第一个参数

我希望调度具有以下优先级:

如果存在公式参数,则使用公式方法(此处的数据参数应为可选参数)

然后,如果未找到公式参数,则如果存在数据参数,请使用data.frame方法(需要x和y参数)

否则foo期望使用x和y参数,否则它将失败。

注意

我想避免如下定义通用函数foo

foo <- function(formula, data,...) UseMethod("foo")

这将解决我的所有问题(我相信所有情况除外,最后一种情况),但这将引发devtools::check()警告,因为某些S3函数将没有与泛型函数相同的参数,并且不再保持一致(特别是foo.default和foo.data.frame)。而且我不想包括缺少的参数,因为这些方法没有用到那些参数。

1 个答案:

答案 0 :(得分:0)

正如Thomas指出的那样,这不是S3类的标准行为。但是,如果您确实想坚持使用S3,则可以编写函数以“模仿” UseMethod,即使它不是很漂亮并且可能不是您想要的。尽管如此,这里的想法是基于首先捕获所有参数,然后检查“首选”参数类型是否存在:

首先获取一些对象:

a <- 1; class(a) <- "Americano"
b <- 2; class(b) <- "Espresso"

让有问题的函数捕获所有带点的参数,然后按照您的喜好顺序检查参数类型是否存在:

drink <- function(...){
  dots <- list(...)

  if(any(sapply(dots, function(cup) class(cup)=="Americano"))){
    drink.Americano(...)
    } else { # you can add more checks here to get a hierarchy
        # try to find appropriate method first if one exists, 
        # using the first element of the arguments as usual
        tryCatch(get(paste0("drink.", class(dots[[1]])))(), 
        # if no appropriate method is found, try the default method:
             error = function(e) drink.default(...)) 
  }
}

drink.Americano <- function(...) print("Hmm, gimme more!")
drink.Espresso <- function(...) print("Tripple, please!")
drink.default <- function(...) print("Any caffeine in there?")

drink(a) # "Americano", dispatch hard-coded.
# [1] "Hmm, gimme more!"
drink(b) # "Espresso", not hard-coded, but correct dispatch anyway
# [1] "Tripple, please!"
drink("sthelse") # Dispatches to default method
# [1] "Any caffeine in there?"
drink(a,b,"c")
# [1] "Hmm, gimme more!"
drink(b,"c", a)
# [1] "Hmm, gimme more!"