Поведение Object obj(args...)
и Object{args...}
зависит от конструкторов, определенных в Object
.
Возьмем следующий пример:
#include <iostream>
#include <initializer_list>
struct A
{
A(int a, int b) {std::cout << "Came to A::A()\n";}
};
struct B
{
B(int a, int b) {std::cout << "Came to B::B(int, int)\n";}
B(std::initializer_list<int> in) {std::cout << "Came to B::B(std::initializer_list<int>)\n";}
};
int main()
{
A a1(10, 20); // Resolves to A(int, int)
A a2{10, 20}; // Resolves to A(int, int)
A a3{30}; // Does not resolve to anything. It's a compiler error.
B b1(10, 20); // Resolves to B(int, int)
B b2{10, 20}; // Resolves to B(std::initializer_list<int> )
B b3{30}; // Resolves to B(std::initializer_list<int> )
}
Вы также можете использовать missing()
для проверки наличия или отсутствия аргумента y
:
fooBar <- function(x,y){
if(missing(y)) {
x
} else {
x + y
}
}
fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3
Есть несколько вариантов, и ни один из них не является официальным правильным способом, и ни один из них не является действительно неправильным, хотя они могут передавать различную информацию на компьютер и другим, читающим ваш код.
Для данного Например, я думаю, что самым ясным вариантом будет предоставление значения по умолчанию для идентификации, в этом случае сделайте что-то вроде:
fooBar <- function(x, y=0) {
x + y
}
. Это самый короткий из показанных до сих пор параметров, а краткость может помочь в удобочитаемости (а иногда даже скорость выполнения). Понятно, что возвращаемое является суммой x и y, и вы можете видеть, что y не получает значение, которое будет 0, которое при добавлении x будет просто результатом x. Очевидно, что если используется что-то более сложное, чем добавление, тогда потребуется другое значение идентификатора (если оно существует).
Мне очень нравится в этом подходе, так как ясно, что значение по умолчанию используется при использовании функцию args
или даже посмотреть файл справки (вам не нужно прокручивать вниз до деталей, это прямо там в использовании).
Недостатком этого метода является то, что значение по умолчанию является сложным (требуется несколько строк кода), то это, вероятно, уменьшит читаемость, чтобы попытаться поместить все это в значение по умолчанию, а подходы missing
или NULL
станут намного более разумными.
Некоторые другие отличия между методами будут отображаться при передаче параметра другой функции или при использовании функций match.call
или sys.call
.
Итак, я думаю, что «правильный» метод зависит о том, что вы планируете делать с этим конкретным аргументом и какую информацию вы хотите передать читателям вашего кода.
Это мои эмпирические правила:
Если значения по умолчанию можно вычислить из других параметров, используйте выражения по умолчанию, как в:
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 generic:
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 = '')
Наконец, если вы выбираете использование ...
, не передавая точки на другую функцию, предупреждайте пользователя о том, что ваша функция игнорирует любые неиспользуемые параметры, поскольку это может быть very запутанным иначе:
fun <- (x,...){
params <- list(...)
optionalParamNames <- letters
unusedParams <- setdiff(names(params),optionalParamNames)
if(length(unusedParams))
stop('unused parameters',paste(unusedParams,collapse = ', '))
blah blah blah
}
NULL
в сигнатуре функции, поскольку удобнее делать функции, которые chain красиво.
– Jthorpe
25 January 2016 в 23:22
Честно говоря, мне нравится первый способ OP начать его с помощью значения NULL
, а затем проверить его с помощью is.null
(в первую очередь потому, что это очень просто и легко понять). Возможно, это зависит от того, как люди привыкли к кодированию, но Хэдли, похоже, поддерживает и путь is.null
:
Из книги Хэдли «Advanced-R» Глава 6, Функции, стр.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 (), чтобы проверить, был ли предоставлен аргумент.
NULL
, и, вероятно, именно поэтому я больше привык к нему, когда вижу исходные коды. Мне кажется более естественным. Тем не менее, как вы говорите, база R принимает оба подхода так, это действительно сводится к индивидуальным предпочтениям.
– LyzandeR
6 February 2015 в 17:55
is.null
, так и missing
в зависимости от контекста и для чего используется аргумент.
– SimonG
5 November 2015 в 11:20
Я бы предпочел использовать NULL для ясности того, что требуется и что необязательно. Одно слово предупреждения об использовании значений по умолчанию, которые зависят от других аргументов, как это предлагает Джторп. Значение не задается при вызове функции, но когда аргумент сначала ссылается! Например:
foo <- function(x,y=length(x)){
x <- x[1:10]
print(y)
}
foo(1:20)
#[1] 10
С другой стороны, если вы ссылаетесь на y перед изменением x:
foo <- function(x,y=length(x)){
print(y)
x <- x[1:10]
}
foo(1:20)
#[1] 20
Это немного опасно, потому что это затрудняет отслеживание о том, что инициализируется «y», как если бы она не вызывалась раньше в функции.
Просто хотелось указать, что встроенная функция 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))
}
}
missing()
также более выразителен в том смысле, что он «говорит, что это значит». Плюс это позволяет пользователям передавать значение NULL, в тех местах, где это имеет смысл! – Josh O'Brien 6 February 2015 в 17:32@param x numeric; something something; @param y numeric; **optional** something something; @param z logical; **optional** something something
– rawr 7 February 2015 в 19:21missing()
ужасен, когда вы хотите передавать аргументы из одной функции в другую. – John Smith 15 September 2016 в 20:15