2013-03-07 11 views
14

Ich habe eine Funktion R:Prevent Teil Argument passende

myFunc <- function(x, base='') { 
} 

ich jetzt erstreckt, die Funktion, eine Menge von beliebigen zusätzlichen Argumente ermöglicht:

myFunc <- function(x, base='', ...) { 
} 

Wie kann ich Teil Argument Anpassung deaktivieren auf dem base Parameter? Ich kann die ... nicht vor base='' setzen, weil ich Rückwärtskompatibilität der Funktion beibehalten möchte (es wird oft als myFunction('somevalue', 'someothervalue') ohne base benannt, das ausdrücklich genannt wird).

wurde ich durch den Aufruf meiner Funktion wie so gestochen:

myFunc(x, b='foo') 

ich dieses base='', b='foo' bedeuten wollen , aber R verwendet Teilübereinstimmung und übernimmt base='foo'.

Gibt es einen Code, den ich in myFunc einfügen kann, um zu bestimmen, was Argument Namen wurden in weitergegeben und nur paßt die genaue „Basis“ die base Parameter, Gruppierung es sonst als Teil der ... in?

+0

Sie könnten vielleicht 'sys.call()' verwenden und von dort aus überprüfen, wie die Funktion aufgerufen wurde. –

+5

Wie wäre es, die neue API 'myFunc2' aufzurufen und die alte (' myFunc') genau so zu behalten, wie es für die Abwärtskompatibilität ist (aber die Implementierung als einfachen Wrapper um 'myFunc2' zu ändern)? – NPE

Antwort

4

Hier ist eine Idee:

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] "" 
+0

Ich erwähnte bereits in der Frage, dass ich die "..." vor "base" nicht für Rückwärtskompatibilität verschieben wollte. Ich lehne mich dem Vorschlag von @ Hemmo zu, obwohl ich vermute, dass ich am Ende mit @ NPEs arbeiten werde. –

+0

@ mathematic.coffee Ich habe gesehen, dass dies das eine Rückwärtskompatibilitätsproblem angeht, das Sie angesprochen haben (d. H., Dass Benutzer sich auf den Positionsabgleich verlassen, um übergebene Werte an das "Basis" -Argument zu übergeben). –

+0

oh, sorry, habe das '.BASE' Bit nicht gesehen! Ich würde dies für jedes optionale Argument tun, oder? –

-1

dies ist ein ekelhaft schreckliche Hack, aber es könnte den Job erledigen:

myFunc <- function(x, base='', b=NULL, ba=NULL, bas=NULL, ...) { 
    dots <- list(b=b, ba=ba, bas=bas, ...) 
    #.. 
} 
1

eingetroffen auf einem anderen Weg, dies zu lösen, veranlasst durch @Hemmo .

Verwenden sys.call() zu wissen, wie myFunc (ohne Teil Argument Matching, match.call dafür verwenden) genannt wurde:

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)) . 

} 
0

Das ist ein bisschen zu spät. Aber für zukünftige Referenz habe ich diese Idee.

Die teilweise Übereinstimmung kann vermieden werden, indem zitierte Namen verwendet werden. Verwenden Sie in einer Funktion sys.call() für die Parameter.

> 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 
> 
0

Es ist möglich, sys.call() zu verwenden, um Zugang zu Funktionsargumenten vom Anrufer als gegeben zu bekommen. Vorsicht ist geboten, da sys.call() die Argumente nicht auswertet und Ihnen statt dessen der Ausdruck des Aufrufs angezeigt wird. Dies wird besonders schwierig, wenn die Funktion mit ... als Argument aufgerufen wird: Die sys.call() enthält nur die ..., nicht ihren Wert. Es ist jedoch möglich, die sys.call() zu nehmen und sie als Argumentliste für eine andere Funktion auszuwerten, beispielsweise list(). Dies wertet alle Versprechen aus und verwirft einige Informationen, aber ich kann nicht erkennen, wie ich das umgehen kann, wenn ich versuche, das interne Matching von R zu umgehen.

Eine Idee wäre, strikte Übereinstimmung zu simulieren.Ich hängten eine Hilfsfunktion, die dies tut genau das, wenn als der erste Befehl in einer Funktion aufgerufen:

fun = function(x, base='', ...) { 
    strictify() # make matching strict 

    list(x, base, ...) 
} 

Diese filtert Nonmatching argumente:

> fun(10, b = 20)                                             
[[1]]                                                
[1] 10 

[[2]] 
[1] "" 

$b 
[1] 20 

und auch in den meisten anderen Fällen funktionieren sollte (mit oder ohne ..., mit Argumenten rechts von der ..., mit Argument Standardwerte). Die einzige Sache, mit der es nicht arbeitet, ist eine Nicht-Standard-Bewertung, z. wenn versucht wird, den Ausdruck eines Arguments mit substitute(arg) zu erhalten.

Die Hilfsfunktion

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[["..."]] 
    } 
} 

Es wäre auch möglich, eine Funktion zu haben, die eine normalerweise Anpaßfunktion in eine streng Anpaßfunktion umwandelt, z.B.

strict.fun = strictificate(fun) 

aber das würde die gleichen Arten von Tricks verwenden.