在R函数中指定可选参数的“正确”方法

时间:2015-02-06 16:23:34

标签: r function

我感兴趣的是"正确"在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,则该函数会添加两个数字。

或者,可以像这样编写函数(其中第二个参数需要通过名称指定,但也可以unlist(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

我个人更喜欢第一个版本。但是,我可以看到两者的好坏。第一个版本不太容易出错,但第二个版本可用于合并任意数量的选项。

是否有正确的&#34;在R中指定可选参数的方法?到目前为止,我已经确定了第一种方法,但两者偶尔会感觉到一点&#34; hacky&#34;。

7 个答案:

答案 0 :(得分:100)

您还可以使用missing()来测试是否提供了参数y

fooBar <- function(x,y){
    if(missing(y)) {
        x
    } else {
        x + y
    }
}

fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3

答案 1 :(得分:44)

说实话,我喜欢OP实际使用NULL值启动它的第一种方法,然后使用is.null进行检查(主要是因为它非常简单易懂) 。这可能取决于人们习惯编码的方式,但Hadl​​ey似乎也支持is.null方式:

来自哈德利的书&#34; Advanced-R&#34;第6章,功能,第84页(对于在线版本检查here):

  

您可以使用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()来检查参数是否已提供。

答案 2 :(得分:22)

这些是我的经验法则:

如果可以从其他参数计算默认值,请使用默认值 表达式如:

fun <- function(x,levels=levels(x)){
    blah blah blah
}

否则使用缺失

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 
}

答案 3 :(得分:7)

有几个选项,其中没有一个是正式的正确方法,但没有一个是非常不正确的,尽管它们可以向计算机和其他读取代码的人传达不同的信息。

对于给定的示例,我认为最明确的选择是提供身份默认值,在这种情况下执行以下操作:

fooBar <- function(x, y=0) {
  x + y
}

这是迄今为止所示选项中最短的选项,而且简洁性有助于提高可读性(有时甚至可以提高执行速度)。很明显,返回的是x和y的总和,你可以看到y没有给出一个0的值,当加到x时它只会产生x。显然,如果使用比添加更复杂的东西,则需要不同的身份值(如果存在)。

我真正喜欢这种方法的一件事是,使用args功能,或者甚至查看帮助文件时,很清楚默认值是什么(您不需要向下滚动到细节,它就在使用中。)

这种方法的缺点是当默认值很复杂(需要多行代码)时,它可能会降低可读性,试图将所有这些都放入默认值和missing或{{1方法变得更加合理。

当参数传递给另一个函数或使用NULLmatch.call函数时,方法之间会出现一些其他差异。

所以我想&#34;正确&#34;方法取决于您打算如何处理该特定参数以及您希望向读者传达的代码信息。

答案 4 :(得分:6)

我倾向于使用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

这有点危险,因为它很难跟踪什么&#34; y&#34;正在初始化,好像它没有在函数的早期调用。

答案 5 :(得分:6)

只是想指出内置的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))
    }
}

答案 6 :(得分:1)

怎么样?

fun <- function(x, ...){
  y=NULL
  parms=list(...)
  for (name in names(parms) ) {
    assign(name, parms[[name]])
  }
  print(is.null(y))
}

然后尝试:

> fun(1,y=4)
[1] FALSE
> fun(1)
[1] TRUE