R言語関数ファクトリー:安全を確保する方法?

2020-08-01 r function functional-programming namespaces

問題

Rの関数ファクトリが「安全」かどうかを確認したいと思います。ここで「安全」とは、ファクトリによって作成された関数の結果が、グローバル変数ではなく、引数にのみ依存することを意味します。

説明

これは危険なファクトリです:

funfac_bad = function(){  
  newfun = function()
    return(foo)
  return(newfun)
}

newfunの戻り値は、newfunの実行時のfooの値に依存します。 fooが未定義の場合、エラーが発生することもあります。

さて-非常に明らかに-このファクトリは、fooをファクトリ内の値にバインドすることによって安全にすることができます

funfac_good = function(){
  foo = 4711
  newfun = function()
    return(foo)
  return(newfun)
}

工場でグローバル変数をチェックすることで安全性を検証できると思いました。本当に:

> codetools::findGlobals(funfac_bad) 
[1] "{"      "="      "foo"    "return"
> codetools::findGlobals(funfac_good)
[1] "{"      "="      "return"

しかし、私の実際の使用例は(はるかに)より複雑です。ファクトリーの関数は、数百行のコードを持つサブ関数と変数に依存しています。したがって、私は定義を入手し、私の工場は原則として次のようになります:

funfac_my = function(){
  sys.source("file_foo.R", envir = environment())
  newfun = function()
    return(foo)
  return(newfun)
}

これは、「file_foo.R」で実行されたコードが名前「foo」を値にバインドする場合にのみ安全なファクトリです。 しかし、(かなり論理的には) codetools::findGlobalsは常に「foo」をグローバル変数として報告します。

質問

定義が提供されているときに、そのような関数ファクトリの安全でない動作をどのように検出できますか?

Answers

外部ファイルを調達する前に、 fooデフォルト値をローカルで定義するようにしてください。たとえば、次のファイルがあるとします。

foo.R

foo <- "file foo"

そしてこのファイル

bar.R

bar <- "bar"

関数ファクトリを次のように書くと:

funfac_my <- function(my_path) {
  foo <- "fun fac foo"
  if(!missing(my_path)) sys.source(my_path, envir = environment())
  function() foo
}

次に、次の結果が得られます。

foo <- "global foo"

funfac_my("foo.R")()
#> [1] "file foo"

funfac_my("bar.R")()
#> [1] "fun fac foo"

funfac_my()()
#> [1] "fun fac foo"

したがって、出力は、「foo」と呼ばれるグローバル環境にオブジェクトがあるかどうかに依存することは決してありません(悪意を持って実行しているスクリプトが「foo」と呼ばれるグローバルをコピーしてコピーしない限り-しかし、それはおそらくあなたが望むものですとにかくそのファイルを調達することにより)

最終行の直前にif(foo == "fun fac foo") stop("object 'foo' not found")という行を追加することで、デフォルト値を返す代わりにこれをエラーをスローするように設定できることに注意してください。したがって、グローバルワークスペースにfooと呼ばれる間違ったオブジェクトがあっても、 fooが見つからないというfooが表示されます。

「定義が提供されているときに、このような関数ファクトリの安全でない動作をどのように検出できるでしょうか?」答えはできないと思いますが、少し変更すると簡単になります。

たとえば、現在、

foo <- undefined_value

"file_foo.R"の唯一の行として、 undefined_valueの使用について警告されたい。私の提案はあなたがそれをしないことです。代わりに、 funfac_my定義全体を"file_foo.R" 、その1行をラップします。

funfac_my = function(){
 
  foo <- undefined_value

  newfun = function()
    return(foo)
  return(newfun)
}

これで、そのファイルをソースし、関数funfac_mycodetools::findGlobalsに渡すことができます。

codetools::findGlobals(funfac_my)
#> [1] "{"               "<-"              "="               "return"         
#> [5] "undefined_value"

Related