我正在开发R中的框架,并希望我的一个功能是多用途的。我想通过要求传递一组参数来实现这一点。换句话说,我想编写一个函数foo
,它需要参数x
和y
或参数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()
并且没有参数默认值,但这绝不是必需的。我可以灵活地使用各种格式,只要它们为手头的问题提供了一个很好的解决方案。
答案 0 :(得分: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
仅当参数集由差异类区分时才有效。例如,如果x
是data.frame
且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函数都使用此调度,包括c
,print
和str
。
答案 1 :(得分: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_a
和foo_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_arg
和a_arg
来构建规范化的接口对象,即从(x)执行问题转换,y)或(a)规范问题。