Предотвращение частичного согласования аргументов
У меня есть функция R:
myFunc <- function(x, base='') {
}
Теперь я расширяю функцию, позволяя набор произвольных дополнительных аргументов:
myFunc <- function(x, base='', ...) {
}
Как отключить частичное согласование аргументов в параметре base
? Я не могу поместить ...
до base=''
, потому что я хотел бы поддерживать обратную совместимость функции (это часто называемый myFunction('somevalue', 'someothervalue')
без base
, который явно указан).
Меня ужалило, вызвав мою функцию следующим образом:
myFunc(x, b='foo')
Я хочу, чтобы это означало base='', b='foo'
, но R использует частичное совпадение и предполагает base='foo'
.
Есть ли какой-нибудь код, который я могу вставить в myFunc
, чтобы определить, какие имена аргументов были переданы и соответствуют только "базовому" параметру base
, иначе группируя его как часть ...
?
Ответы
Ответ 1
Вот идея:
myFunc <- function(x, .BASE = '', ..., base = .BASE) {
base
}
## Takes fully matching named arguments
myFunc(x = "somevalue", base = "someothervalue")
# [1] "someothervalue"
## Positional matching works
myFunc("somevalue", "someothervalue")
# [1] "someothervalue"
## Partial matching _doesn't_ work, as desired
myFunc("somevalue", b="someothervalue")
# [1] ""
Ответ 2
Только что пришел по другому пути, чтобы решить эту проблему, вызванную @Hemmo.
Используйте sys.call()
, чтобы узнать, как был вызван myFunc
(без частичного согласования аргументов, используйте для этого match.call
):
myFunc <- function(x, base='', ...) {
x <- sys.call() # x[[1]] is myFunc, x[[2]] is x, ...
argnames <- names(x)
if (is.null(x$base)) {
# if x[[3]] has no argname then it is the 'base' argument (positional)
base <- ifelse(argnames[3] == '', x[[3]], '')
}
# (the rest of the `...` is also in x, perhaps I can filter it out by
# comparing argnames against names(formals(myFunc)) .
}
Ответ 3
Это немного поздно. Но для будущей справки я получил эту идею.
Частичное совпадение можно избежать, используя цитированные имена. В функции используйте sys.call() для параметров.
> myFunc <- function(x, base="base", ...) {
+ ## get the arguments
+ ss=sys.call()
+
+ ## positional arguments can be retrieved using numbers
+ print(paste("ss[[2]]=",ss[[2]]))
+
+ ## named arguments, no partial matching
+ print(ss[['base']]) ## NULL
+
+ ## named arguments, no partial matching
+ print(ss[['b']]) ## "a"
+
+ ## regular call, partially matched
+ print(base) ## "a"
+
+ ## because 'b' is matched to 'base',
+ ## 'b' does not exist, cause an error
+ print(b)
+ }
>
> myFunc(x=1,b='a')
[1] "ss[[2]]= 1"
NULL
[1] "a"
[1] "a"
Error in print(b) : object 'b' not found
> myFunc(1,base="b")
[1] "ss[[2]]= 1"
[1] "b"
NULL
[1] "b"
Error in print(b) : object 'b' not found
> myFunc(2,"c")
[1] "ss[[2]]= 2"
NULL
NULL
[1] "c"
Error in print(b) : object 'b' not found
>
Ответ 4
Можно использовать sys.call()
, чтобы получить доступ к аргументам функции, указанным вызывающим. Необходимо соблюдать осторожность, поскольку sys.call()
не оценивает аргументы и вместо этого дает вам выражение вызова. Это становится особенно трудным, когда функция вызывается с помощью ...
в качестве аргумента: sys.call()
будет содержать только ...
, а не их значение. Тем не менее, можно взять sys.call()
и оценить его как список аргументов для другой функции, например list()
. Это оценивает все promises и отбрасывает некоторую информацию, но я не вижу, как обойти это, пытаясь обойти внутреннее соответствие R.
Одной из идей было бы симулировать строгое соответствие. Я добавил вспомогательную функцию, которая выполняет именно это, если вызывается как первая команда в функции:
fun = function(x, base='', ...) {
strictify() # make matching strict
list(x, base, ...)
}
Отфильтровывает несимметричные аргументы:
> fun(10, b = 20)
[[1]]
[1] 10
[[2]]
[1] ""
$b
[1] 20
и должен также работать в большинстве других случаев (с или без ...
, с аргументами справа от ...
, с значениями по умолчанию для аргументов). Единственное, с чем он не работает, - это нестандартная оценка, например. при попытке получить выражение аргумента с помощью substitute(arg)
.
Функция помощника
strictify <- function() {
# remove argument values from the function
# since matching already happened
parenv <- parent.frame() # environment of the calling function
rm(list=ls(parenv), envir=parenv) # clear that environment
# get the arguments
scall <- sys.call(-1) # 'call' of the calling function
callingfun <- scall[[1]]
scall[[1]] <- quote(`list`)
args <- eval.parent(scall, 2) # 'args' is now a list with all arguments
# if none of the argument are named, we need to set the
# names() of args explicitly
if (is.null(names(args))) {
names(args) <- rep("", length(args))
}
# get the function header ('formals') of the calling function
callfun.object <- eval.parent(callingfun, 2)
callfun.header <- formals(callfun.object)
# create a dummy function that just gives us a link to its environment.
# We will use this environment to access the parameter values. We
# are not using the parameter values directly, since the default
# parameter evaluation of R is pretty complicated.
# (Consider fun <- function(x=y, y=x) { x } -- fun(x=3) and
# fun(y=3) both return 3)
dummyfun <- call("function", callfun.header, quote(environment()))
dummyfun <- eval(dummyfun, envir=environment(callfun.object))
parnames <- names(callfun.header)
# Sort out the parameters that didn't match anything
argsplit <- split(args, names(args) %in% c("", parnames))
matching.args <- c(list(), argsplit$`TRUE`)
nonmatching.arg.names <- names(argsplit$`FALSE`)
# collect all arguments that match something (or are just
# positional) into 'parenv'. If this includes '...', it will
# be overwritten later.
source.env <- do.call(dummyfun, matching.args)
for (varname in ls(source.env, all.names=TRUE)) {
parenv[[varname]] <- source.env[[varname]]
}
if (!"..." %in% parnames) {
# Check if some parameters did not match. It is possible to get
# here if an argument only partially matches.
if (length(nonmatching.arg.names)) {
stop(sprintf("Nonmatching arguments: %s",
paste(nonmatching.arg.names, collapse=", ")))
}
} else {
# we manually collect all arguments that fall into '...'. This is
# not trivial. First we look how many arguments before the '...'
# were not matched by a named argument:
open.args <- setdiff(parnames, names(args))
taken.unnamed.args <- min(which(open.args == "...")) - 1
# We throw all parameters that are unmatched into the '...', but we
# remove the first `taken.unnamed.args` from this, since they go on
# filling the unmatched parameters before the '...'.
unmatched <- args[!names(args) %in% parnames]
unmatched[which(names(unmatched) == "")[seq_len(taken.unnamed.args)]] <- NULL
# we can just copy the '...' from a dummy environment that we create
# here.
dotsenv <- do.call(function(...) environment(), unmatched)
parenv[["..."]] <- dotsenv[["..."]]
}
}
Также возможно иметь функцию, которая преобразует нормально согласующуюся функцию в строго соответствующую функцию, например.
strict.fun = strictificate(fun)
но это будет использовать те же самые трюки.
Ответ 5
Это отвратительно ужасный взлом, но он может выполнить свою работу:
myFunc <- function(x, base='', b=NULL, ba=NULL, bas=NULL, ...) {
dots <- list(b=b, ba=ba, bas=bas, ...)
#..
}