2013-03-07 14 views
14

をマッチング:防止部分の引数私はRの機能を持っている

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

私は今、任意の余分な引数のセットが可能、機能を拡張しています:私は部分的な引数のマッチングを無効にすることがどのように

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

baseパラメータには?...base=''の前に置くことはできません。なぜなら、関数の下位互換性を維持したいからです(baseは明示的に名前を付けずにmyFunction('somevalue', 'someothervalue')と呼ばれることがよくあります)。私はそうのように私の関数を呼び出すことによって刺さしまった

myFunc(x, b='foo') 

私は、これはbase='', b='foo'を意味するをしたいが、Rは、部分一致を使用し、base='foo'を前提としています。

私は名前が渡されたかの引数を決定するためにmyFuncに挿入するだけbaseパラメータに正確な「ベース」と一致し、そうでない場合は...の一部としてそれをグループ化することができますいくつかのコードはありますか?ここで

+0

おそらく 'sys.call()'を使用して、関数がどのように呼び出されたかを調べることができます。 –

+5

新しいAPIの 'myFunc2'を呼び出し、古いもの(' myFunc')を下位互換性のためそのまま維持します(ただし、実装を 'myFunc2'の周りの単純なラッパーに変更します)? – NPE

答えて

4

はアイデアです:

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

私はすでに 'base 'の前に' ... 'を下位互換性のために動かすつもりはないという質問に触れました。 @ Hemmoの提案に向かって、@ NPEを使ってしまうかもしれないと思うが。 –

+0

@ mathematical.coffeeこれは、あなたが提起した1つの後方互換性問題に対処する理由です。つまり、ユーザーは、指定された値を 'base'引数に渡すために位置合致を使用します。 –

+0

ああ、申し訳ありませんが、 '.BASE'ビットが見えませんでした!私が持っていたオプションの議論ごとにこれを行うだろうか? –

-1

これはうんざり恐ろしいハックですが、それは仕事を得るかもしれません:

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

ちょうど@Hemmoによって促さこれを解決する別の方法、に到着しました。 myFuncが(そのためにmatch.callを使用して、部分的な引数のマッチングで)呼ばれたかを知るために

使用sys.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)) . 

} 
0

これは少し遅れています。しかし、将来の参考として、私はこの考えを持っています。

部分一致は、引用符で囲まれた名前を使用することで回避できます。ある関数では、パラメータに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 
> 
0

sys.call()を使用して、呼び出し側から与えられた関数引数にアクセスすることができます。 sys.call()は引数を評価せず、代わりにという式を提供するので、注意が必要です。これは、関数が...を引数として呼び出されたときに特に困難になります。sys.call()には、その値ではなく...が含まれます。ただし、sys.call()をとり、別の関数の引数リストとして評価することもできます(例:list())。これはすべての約束を評価し、いくつかの情報を捨てますが、私はRの内部一致を回避しようとするときにこれを回避する方法を見ることができません。

1つのアイデアは、厳密な一致をシミュレートすることです。これが一致しない引数除外さ

​​

:私は、まさにこの関数の最初のコマンドとして呼び出された場合ないヘルパー関数を追加

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

でも、同じ種類のトリックを使用します。

関連する問題