我感兴趣的是在R中使用可选参数编写函数的“正确”方式。
随着时间的推移,我偶然发现了一些走不同路线的代码片段,我无法找到关于这个主题的适当(官方)立场。
到目前为止,我写的可选参数是这样的:
fooBar <- function(x,y=NULL){
if(!is.null(y)) x <- x+y
return(x)
}
fooBar(3) # 3
fooBar(3,1.5) # 4.5
如果只提供了x,则函数简单地返回其参数。它为第二个参数使用默认的NULL值,如果该参数恰好不是NULL,则该函数将两个数字相加。
或者,可以这样写函数(其中第二个参数需要通过name指定,但也可以取消list(z)或定义z <- sum(…)):
fooBar <- function(x,...){
z <- list(...)
if(!is.null(z$y)) x <- x+z$y
return(x)
}
fooBar(3) # 3
fooBar(3,y=1.5) # 4.5
我个人更喜欢第一个版本。然而,我可以看到两者的优点和缺点。第一个版本不太容易出错,但第二个版本可以用来合并任意数量的可选选项。
在R中是否有一种“正确”的方法来指定可选参数?到目前为止,我已经确定了第一种方法,但这两种方法偶尔都会感觉有点“俗气”。
以下是我的经验法则:
如果其他参数可以计算出默认值,则使用default
表达式如下:
fun <- function(x,levels=levels(x)){
blah blah blah
}
否则使用missing
fun <- function(x,levels){
if(missing(levels)){
[calculate levels here]
}
blah blah blah
}
在极少数情况下,您认为用户可能想要指定一个默认值
它持续整个R会话,使用getOption
fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue)
blah blah blah
}
如果根据第一个参数的类应用了一些参数,
使用S3泛型:
fun <- function(...)
UseMethod(...)
fun.character <- function(x,y,z){# y and z only apply when x is character
blah blah blah
}
fun.numeric <- function(x,a,b){# a and b only apply when x is numeric
blah blah blah
}
fun.default <- function(x,m,n){# otherwise arguments m and n apply
blah blah blah
}
使用……仅当您将附加参数传递给
另一个函数
cat0 <- function(...)
cat(...,sep = '')
最后,如果你选择使用…不要将圆点传递给另一个函数,警告用户你的函数忽略了任何未使用的参数,否则会非常混乱:
fun <- (x,...){
params <- list(...)
optionalParamNames <- letters
unusedParams <- setdiff(names(params),optionalParamNames)
if(length(unusedParams))
stop('unused parameters',paste(unusedParams,collapse = ', '))
blah blah blah
}
只是想指出,内置的sink函数有很好的例子,可以在函数中设置不同的参数:
> sink
function (file = NULL, append = FALSE, type = c("output", "message"),
split = FALSE)
{
type <- match.arg(type)
if (type == "message") {
if (is.null(file))
file <- stderr()
else if (!inherits(file, "connection") || !isOpen(file))
stop("'file' must be NULL or an already open connection")
if (split)
stop("cannot split the message connection")
.Internal(sink(file, FALSE, TRUE, FALSE))
}
else {
closeOnExit <- FALSE
if (is.null(file))
file <- -1L
else if (is.character(file)) {
file <- file(file, ifelse(append, "a", "w"))
closeOnExit <- TRUE
}
else if (!inherits(file, "connection"))
stop("'file' must be NULL, a connection or a character string")
.Internal(sink(file, closeOnExit, FALSE, split))
}
}
老实说,我喜欢OP的第一种方法,实际上开始它与一个NULL值,然后检查它与is。Null(主要是因为它非常简单和容易理解)。这可能取决于人们习惯的编码方式,但Hadley似乎支持is。Null也是:
摘自哈德利的书《高级r》第六章,函数,第84页(网上版本请点击此处查看):
可以通过missing()函数确定是否提供了参数。
i <- function(a, b) {
c(missing(a), missing(b))
}
i()
#> [1] TRUE TRUE
i(a = 1)
#> [1] FALSE TRUE
i(b = 2)
#> [1] TRUE FALSE
i(1, 2)
#> [1] FALSE FALSE
有时您希望添加一个重要的默认值,这可能需要几行代码来计算。如果需要,可以使用missing()有条件地计算它,而不是在函数定义中插入该代码。然而,如果不仔细阅读文档,就很难知道哪些参数是必需的,哪些参数是可选的。相反,我通常将默认值设置为NULL,并使用is.null()来检查是否提供了参数。
以下是我的经验法则:
如果其他参数可以计算出默认值,则使用default
表达式如下:
fun <- function(x,levels=levels(x)){
blah blah blah
}
否则使用missing
fun <- function(x,levels){
if(missing(levels)){
[calculate levels here]
}
blah blah blah
}
在极少数情况下,您认为用户可能想要指定一个默认值
它持续整个R会话,使用getOption
fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue)
blah blah blah
}
如果根据第一个参数的类应用了一些参数,
使用S3泛型:
fun <- function(...)
UseMethod(...)
fun.character <- function(x,y,z){# y and z only apply when x is character
blah blah blah
}
fun.numeric <- function(x,a,b){# a and b only apply when x is numeric
blah blah blah
}
fun.default <- function(x,m,n){# otherwise arguments m and n apply
blah blah blah
}
使用……仅当您将附加参数传递给
另一个函数
cat0 <- function(...)
cat(...,sep = '')
最后,如果你选择使用…不要将圆点传递给另一个函数,警告用户你的函数忽略了任何未使用的参数,否则会非常混乱:
fun <- (x,...){
params <- list(...)
optionalParamNames <- letters
unusedParams <- setdiff(names(params),optionalParamNames)
if(length(unusedParams))
stop('unused parameters',paste(unusedParams,collapse = ', '))
blah blah blah
}
我倾向于使用NULL来明确什么是必需的,什么是可选的。对于使用依赖于其他参数的默认值,有一个警告,就像Jthorpe建议的那样。该值不是在函数调用时设置的,而是在第一次引用实参时设置的!例如:
foo <- function(x,y=length(x)){
x <- x[1:10]
print(y)
}
foo(1:20)
#[1] 10
另一方面,如果你在改变x之前引用y:
foo <- function(x,y=length(x)){
print(y)
x <- x[1:10]
}
foo(1:20)
#[1] 20
这有点危险,因为它很难跟踪初始化的“y”,就好像它没有在函数的早期被调用一样。