2015-02-06 12 views
96

Ich interessiere mich für die "richtige" Möglichkeit, Funktionen mit optionalen Argumenten in R zu schreiben. Im Laufe der Zeit bin ich auf ein paar Codeelemente gestoßen, die etwas anderes nehmen Route hier, und ich konnte keine richtige (offizielle) Position zu diesem Thema finden."Korrekte" Möglichkeit, optionale Argumente in R-Funktionen anzugeben

Bisher habe ich optionale Argumente wie folgt geschrieben:

fooBar <- function(x,y=NULL){ 
    if(!is.null(y)) x <- x+y 
    return(x) 
} 
fooBar(3) # 3 
fooBar(3,1.5) # 4.5 

Die Funktion gibt einfach ihr Argument, wenn nur x geliefert wird. Es verwendet einen Standardwert NULL für das zweite Argument, und wenn dieses Argument nicht NULL ist, fügt die Funktion die zwei Zahlen hinzu.

Alternativ könnte man die Funktion wie folgt schreiben (wobei das zweite Argument von Name angegeben werden muss, aber man könnte auch unlist(z) oder definieren z <- sum(...) statt):

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 

Persönlich bevorzuge ich die erste Version. Aber ich kann mit beiden gut und schlecht sehen. Die erste Version ist ein wenig weniger anfällig für Fehler, aber die zweite könnte verwendet werden, um eine beliebige Anzahl von Optionalen einzubauen.

Gibt es eine "richtige" Möglichkeit, optionale Argumente in R anzugeben? Bis jetzt habe ich mich auf den ersten Ansatz festgelegt, aber beide können sich manchmal ein bisschen "hacky" fühlen.

+0

prüfen, den Quellcode aus 'xy.coords' eine häufig zu sehen benutzter Ansatz. –

+2

Der Quellcode für 'xy.coords', der von [Carl Witthoft] (http://stackoverflow.com/users/884372/carl-witthoft) genannt wird, finden Sie unter [xy.coords] (https: // github. com/wch/r-source/blob/af7f52f70101960861e5d995d3a4bec010bc89e6/src/library/grDevices/R/xyz.coords.R # L21-L126) – ecerulm

Antwort

67

könnten Sie auch missing() verwenden, um zu testen, ob das Argument y geliefert wurde:

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

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

Ich mag es besser zu verpassen. Vor allem, wenn Sie viele NULL-Standardwerte haben, werden Sie nicht x = NULL, y = NULL, z = NULL in Ihrer Paketdokumentation haben – rawr

+0

In der Tat scheint dies der "beabsichtigte" Weg, es zu tun. – SimonG

+4

@rawr 'missing()' ist auch ausdrucksstark in dem Sinne, dass es "sagt, was es bedeutet". Außerdem erlaubt es Benutzern, einen Wert von NULL an Stellen einzugeben, an denen dies sinnvoll ist! –

31

Um ehrlich zu sein, ich den ersten Weg des OP mag tatsächlich mit einem NULL Wert beginnen und es dann mit is.null Überprüfung (vor allem, weil es sehr einfach und leicht zu verstehen ist). Es hängt vielleicht auf dem Weg, um Menschen zu Codierung verwendet werden, aber die Hadley scheint auch die is.null Art und Weise zu unterstützen:

Von Hadley Buch „Advanced-R“ Kapitel 6, Funktionen, S.84 (für die Online-Versionsprüfung here) :

Sie können feststellen, ob ein Argument mit der Funktion missing() angegeben wurde oder nicht.

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 

Manchmal möchte man eine nicht-triviale Standardwert hinzuzufügen, die mehrere Zeilen Code nehmen könnte zu berechnen. Anstatt diesen Code in die Funktionsdefinition einzufügen, könnten Sie missing() verwenden, um ihn bei Bedarf bedingt zu berechnen. Dies macht es jedoch schwierig zu wissen, welche Argumente erforderlich sind und welche optional sind, ohne die Dokumentation sorgfältig zu lesen. Stattdessen setze ich normalerweise den Standardwert auf NULL und verwende is.null(), um zu überprüfen, ob das Argument angegeben wurde.

+0

Interessant. Das hört sich vernünftig an, aber sind Sie jemals verwirrt darüber, welche Argumente für eine Funktion erforderlich sind und welche optional sind? Ich bin mir nicht sicher, ob ich jemals diese Erfahrung gemacht habe ... –

+1

@ JoshO'Brien Ich denke, ich hatte dieses Problem mit beiden Codierungsstil nicht um ehrlich zu sein, zumindest war es nie ein großes Problem, wahrscheinlich wegen der Dokumentation oder Lesen des Quellcodes. Und deshalb sage ich vor allem, dass es wirklich um den Codierstil geht, den Sie gewohnt sind. Ich benutze die 'NULL'-Methode schon seit einer ganzen Weile und wahrscheinlich bin ich deshalb daran gewöhnt, wenn ich Quellcodes sehe. Es scheint mir natürlicher. Das heißt, wie Sie sagen, nimmt Base R beide Ansätze, so kommt es wirklich auf individuelle Präferenzen. – LyzandeR

+1

Inzwischen wünschte ich wirklich, ich könnte zwei Antworten als richtig markieren, weil das, was ich wirklich erreicht habe, sowohl "is.null" als auch "missing" verwendet, abhängig vom Kontext und dem, wofür das Argument verwendet wird. – SimonG

19

Das sind meine Faustregeln:

Wenn Standardwerte können von anderen Parametern berechnet werden, verwenden Sie die Standard Ausdrücke wie:

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

sonst, wenn sie fehlt

mit
fun <- function(x,levels){ 
    if(missing(levels)){ 
     [calculate levels here] 
    } 
    blah blah blah 
} 

In der seltene Wenn Sie selbst, was ein Benutzer einen Standardwert angeben möchten, können , die eine ganze R Sitzung dauert, verwenden getOption

fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue) 
    blah blah blah 
} 

Wenn einige Parameter gelten für die Klasse des ersten Arguments abhängig, eine S3-generic verwenden:

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 
} 

Verwenden ... nur, wenn Sie zusätzliche Parameter auf eine andere Funktion

cat0 <- function(...) 
    cat(...,sep = '') 
sind vorbei

Schließlich, wenn Sie die ... Gebrauch machen wählen, ohne die Punkte auf eine andere Funktion übergeben, warnen den Benutzer, dass Ihre Funktion alle nicht verwendeten Parameter ignoriert da es sehr verwirrend anders sein kann:

fun <- (x,...){ 
    params <- list(...) 
    optionalParamNames <- letters 
    unusedParams <- setdiff(names(params),optionalParamNames) 
    if(length(unusedParams)) 
     stop('unused parameters',paste(unusedParams,collapse = ', ')) 
    blah blah blah 
} 
+0

die s3-Methode Option war eines der ersten Dinge, die mir in den Sinn kam, auch – rawr

+0

Rückblickend habe ich die OP-Methode der Zuweisung von 'NULL' in der Funktion Unterschrift, wie es bequemer für die Herstellung von Funktionen, die [ Kette] (https://en.wikipedia.org/wiki/Method_chaining) schön. – Jthorpe

7

Es gibt mehrere Optionen und keine von ihnen ist die offizielle korrekte Art und keine von ihnen sind wirklich falsch, obwohl sie unterschiedliche Informationen an den Computer und an andere übermitteln können, die Ihren Code lesen.

Für das gegebene Beispiel denke ich, die klarste Möglichkeit wäre, eine Identität Standardwert zu liefern, in diesem Fall so etwas wie:

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

Dies ist die kürzest der Optionen bisher gezeigt und Kürze kann helfen Lesbarkeit (und manchmal sogar Geschwindigkeit in der Ausführung). Es ist klar, dass das, was zurückgegeben wird, die Summe von x und y ist, und Sie können sehen, dass y keinen Wert erhält, der 0 sein wird, was, wenn er zu x addiert wird, nur zu x führt. Offensichtlich wird, wenn etwas komplizierter als die Addition verwendet wird, ein anderer Identitätswert benötigt (falls einer existiert).

Eine Sache, die ich wirklich gerne über diesen Ansatz ist, dass es klar ist, was der Standardwert ist, wenn Sie die args Funktion verwenden oder sogar auf die Hilfedatei schauen (Sie müssen nicht zu den Details scrollen, es ist genau dort in der Nutzung). Der Nachteil dieser Methode ist, wenn der Standardwert komplex ist (mehrere Codezeilen erfordern), dann würde es wahrscheinlich die Lesbarkeit reduzieren, um all das in den Standardwert zu bringen und die Ansätze missing oder NULL werden viel vernünftiger .

Einige der anderen Unterschiede zwischen den Methoden werden angezeigt, wenn der Parameter an eine andere Funktion übergeben wird oder wenn die Funktionen match.call oder sys.call verwendet werden.

Also ich denke, die "richtige" Methode hängt davon ab, was Sie mit diesem bestimmten Argument vorhaben und welche Informationen Sie den Lesern Ihres Codes übermitteln möchten.

6

Ich würde eher die Verwendung von NULL für die Klarheit dessen, was erforderlich ist und was optional ist. Ein Wort der Warnung über die Verwendung von Standardwerten, die von anderen Argumenten abhängen, wie von Jthorpe vorgeschlagen. Der Wert wird nicht gesetzt, wenn die Funktion aufgerufen wird, aber wenn das Argument zum ersten Mal referenziert wird! Zum Beispiel:

foo <- function(x,y=length(x)){ 
    x <- x[1:10] 
    print(y) 
} 
foo(1:20) 
#[1] 10 

Auf der anderen Seite, wenn Sie y vor dem Wechsel x Referenz:

foo <- function(x,y=length(x)){ 
    print(y) 
    x <- x[1:10] 
} 
foo(1:20) 
#[1] 20 

Das ist ein bisschen gefährlich, weil sie es schwer, den Überblick zu behalten, was „y“ machen, ist initialisiert werden, als ob es nicht früh in der Funktion aufgerufen wird.

2

Ich wollte nur darauf hinweisen, dass die eingebauten in sink Funktion gute Beispiele für verschiedene Möglichkeiten hat Argumente in einer Funktion zu setzen:

> 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)) 
    } 
} 
Verwandte Themen