1

我正在用 R 开发一个框架,并希望我的一个功能是多用途的。我想通过要求传递一组参数来实现这一点。换句话说,我想编写一个foo需要 argumentsxyOR the argument 的函数a。如果两个集合都没有提供,如果一个集合不完整,或者两个集合都提供,则应该抛出错误。

实现此目的的一种方法是仅使用可选参数,后跟if语句。这在下面演示。但是,我想更优雅地做到这一点。

foo <- function(x, y, a){
  if (!missing(a) & missing(x) & missing(y)){
    a
    return("Only a provided")
  } else if (missing(a) & !missing(x) & !missing(y)){
    x; y
    return("x and y provided")
  } else {
  stop("No complete and/or distinct argument set provided")
}

该功能应按如下方式工作:

> foo(a = 1)
[1] "Only a provided"

> foo(x = 1, y = 2)
[1] "x and y provided"

> foo()
Error in foo() : No complete and/or distinct argument set provided

> foo(x = 1)
Error in foo(x = 1) : No complete and/or distinct argument set provided

> foo(x = 1, y = 2, a = 3)
Error in foo(x = 1, y = 2, a = 3) : 
  No complete and/or distinct argument set provided

额外的功劳还包括一个可以处理任意数量、任意大小的参数集的通用答案。

另外:上面的示例使用missing()并且没有参数默认值,但这绝不是要求。我可以灵活地使用各种格式,只要它们能很好地解决手头的问题。

4

2 回答 2

1

从我的评论来看,有两个想法。

missing

这本质上是您的方法,稍作修改(主要是为了样式和/或可读性,只是为了美观):

foo1 <- function(x, y, z, a, b) {
  # first argument set
  allFirst <- ! any(missing(x), missing(y), missing(z))
  anyFirst <- any(! missing(x), ! missing(y), ! missing(z))
  # second argument set
  allSecond <- ! any(missing(a), missing(b))
  anySecond <- any(! missing(a), ! missing(b))

  if ( (allFirst && anySecond) ||
         (allSecond && anyFirst))
    stop("provide either arguments x,y,z or a,b", call. = FALSE)
  if ( (anyFirst && ! allFirst) ||
         (anySecond && ! allSecond) )
    stop("no complete and/or distinct argument set provided", call. = FALSE)

  if (allFirst) {
    return("x,y,z provided")
  } else if (allSecond) {
    return("a,b provided")
  } else {
    stop("nothing provided", call. = FALSE)
  }  
}
foo1(a = 1, b = 2)
# [1] "a,b provided"
foo1(x = 1, y = 2, z = 3)
# [1] "x,y,z provided"
foo1()
# Error: nothing provided
foo1(x = 1)
# Error: no complete and/or distinct argument set provided
foo1(a = 1)
# Error: no complete and/or distinct argument set provided
foo1(x = 1, b = 2)
# Error: no complete and/or distinct argument set provided

S3 方法分派

这仅在参数集由不同类区分时才有效。例如,如果x是 adata.frame并且a是 a list,那么...

请注意,第一个定义(启用其他定义)设置公共参数,因此所有函数都需要x用作第一个参数:

foo2 <- function(x, ...) UseMethod("foo2", x)
foo2.data.frame <- function(x, y, z) {
  if (missing(y) || missing(z)) stop("no complete and/or distinct argument set provided for 'x'", call. = FALSE)
  return("x,y,z provided")
}
foo2.list <- function(x, b, ...) {
  if (missing(b)) stop("no complete and/or distinct argument set provided for 'a'", call. = FALSE)
  return("a,b provided")
}

...所以我们不能function(a, b)在正式定义中使用。

foo2(x = data.frame(), y = 1, z = 2)
# [1] "x,y,z provided"
foo2(x = list(), b = 1)
# [1] "a,b provided"
foo2(data.frame())
# Error: no complete and/or distinct argument set provided for 'x'
foo2(x = list())
# Error: no complete and/or distinct argument set provided for 'a'
foo2()
# Error in foo2() (from #1) : argument "x" is missing, with no default
foo2(x=data.frame(), b=2)
# Error in foo2.data.frame(x = data.frame(), b = 2) (from #1) : 
#   unused argument (b = 2)

在第一个函数中需要使用省略号...,但在其他两个函数中,它有点风格并且可能不是必需的,因为它允许将一些参数传递给其他伴随/依赖函数。

此处的错误消息应该更具描述性,因为(至少)所有函数都将假定第一个参数为x(而不是a)。

此选项正在执行一种称为多态的东西,其中函数的行为根据所提供的数据类别有很大不同。如果它总是返回相同类型的对象,这会减少一点,但即使这样,有些人还是觉得它不受欢迎。

请注意,许多标准 R 函数都使用此调度,包括cprintstr

于 2017-10-16T03:47:12.580 回答
1

@r2evans 显示的方法的另一个选择是用于...调度:

foo <- function(...) {
    args <- list(...)
    if("a" %in% names(args) && "x" %in% names(args) && y %in% names(args))
        stop("need either 'a' or 'x' and 'y' arguments.")
    if("a" %in% names(args)) return(foo_a(a=args[["a"]]))
    if("x" %in% names(args) && "y" %in% names(args)) return(foo_xy(x=args[["x"]], y=args[["y"]])
    stop("need either 'a' or 'x' and 'y' arguments.")
}

您需要定义foo_afoo_xy进行实际计算。这种方法的缺点是它只适用于命名参数;调用foo(2,3), 而不是foo(x=2, y=3)会导致错误。这可以通过查看args上面代码中的长度来解决,但是如果您处理越来越多的参数,这很快就会变得混乱。

另一种选择是将参数集收集到(S3 或 S4)对象中,并在参数集类上分派,如下所示

xy_arg <- function(x,y) {
    ans <- list(x=x, y=y)
    class(ans) <- "xy_arg"
    return(ans)
}
a_arg - function(a) {
    ans <- list(a=a)
    class(ans) <- "a_arg"
    return(ans)
}
foo <- function(x, ...) UseMethod("foo", x)
foo.xy_arg <- function(x, ...) { 
    #compute for argument set where x and y is given
    print(x[["x"]], x[["y"]])
}
foo.a_arg <- function(x, ...) 
    print(x[["a"]])
}
foo(xy_arg(x=1, y=2))
foo(a_arg(a=3))

起初这看起来更复杂,但是它允许以系统的方式定义更多的参数集。`

也可以定义foo为仅在一个参数集上工作并使用xy_arga_arg构建规范化接口对象,即执行从 (x,y) 或 (a) 到规范问题的问题转换。

于 2017-10-16T09:11:14.307 回答