44

仲間の学生との最近の会話で、私は定数を保存することを除いてグローバルを避けることを提唱してきました。これは、誰もが独自のコードを記述し、プロジェクトのサイズが小さいという典型的な応用統計タイプのプログラムの一種であるため、雑な習慣によって引き起こされる問題を人々が理解するのは難しい場合があります。

グローバルの回避について話すとき、私はグローバルが問題を引き起こす可能性がある次の理由に焦点を当てていますが、原則(およびあなたが重要だと思うかもしれない他の原則)に沿ってRおよび/またはStataでいくつかの例を示したいと思います)、そして私は信頼できるものを思い付くのに苦労しています。

  • 非局所性:グローバルはコードの流れを理解しにくくするため、デバッグが難しくなります
  • 暗黙的な結合:グローバルは、コードの離れたセグメント間の複雑な相互作用を可能にすることにより、関数型プログラミングの単純さを破ります
  • 名前空間の衝突:一般名(x、iなど)が再利用され、名前空間の衝突が発生します

この質問に対する有用な答えは、グローバルが特定のタイプの問題を引き起こす、再現可能で自己完結型のコードスニペットであり、理想的には、問題が修正された別のコードスニペットを使用します。必要に応じて修正された解を生成できるので、問題の例がより重要です。

関連リンク

グローバル変数が悪い

グローバル変数は悪いですか?

4

10 に答える 10

29

また、プログラミングの経験がない学部生にRを教えることもできて嬉しいです。私が見つけた問題は、グローバルが悪い場合のほとんどの例は、かなり単純であり、実際には意味がわからないということでした。

代わりに、驚き最小の原則を説明しようとしています。何が起こっているのかを理解するのが難しい例を使用します。ここではいくつかの例を示します。

  1. 私はクラスに、最終的な価値がどうなると思うかを書き留めてもらいiます。

    i = 10
    for(i in 1:5)
        i = i + 1
    i
    

    一部のクラスは正しく推測します。それでは、このようなコードを書くべきでしょうか?

    ある意味でiは、変更されているグローバル変数です。

  2. 次のコードは何を返しますか?

    x = 5:10
    x[x=1]
    

    問題は、私たちが正確に何を意味するのかということですx

  3. 次の関数はグローバル変数またはローカル変数を返しますか?

     z = 0
     f = function() {
         if(runif(1) < 0.5)
              z = 1
         return(z)
      }
    

    回答:両方。なぜこれが悪いのかをもう一度話し合ってください。

于 2011-04-03T08:41:09.877 に答える
18

ああ、グローバルの素晴らしい匂い...

この投稿のすべての回答はRの例を示しており、OPはいくつかのStataの例も望んでいました。だから私はこれらとチャイムを入れましょう。

Rとは異なり、Stataはローカルマクロ(localコマンドで作成したマクロ)の局所性を処理するため、「これはグローバルzですか、それとも返されるローカルzですか?」という問題があります。決して出てこない。(まあ...ローカリティが強制されていない場合、Rの人はどのようにコードを書くことができますか?)しかし、Stataには別の癖があります。つまり、存在しないローカルまたはグローバルマクロは空の文字列として評価されます。望ましい場合と望ましくない場合があります。

私はいくつかの主な理由でグローバルが使用されているのを見てきました:

  1. グローバルは、次のように変数リストのショートカットとしてよく使用されます。

    sysuse auto, clear
    regress price $myvars
    

このような構成の主な用途は、複数の仕様を試すときに、対話型の入力とコードのdoファイルへの格納を切り替える人のためだと思います。等分散性標準誤差、不均一分散性標準誤差、および中央値回帰を使用して回帰を試みたとします。

    regress price mpg foreign
    regress price mpg foreign, robust
    qreg    price mpg foreign

そして、これらの回帰を別の変数セットで実行し、次にさらに別の変数セットで実行し、最後にこれをあきらめて、次のようなdoファイルとして設定しますmyreg.do

    regress price $myvars
    regress price $myvars, robust
    qreg    price $myvars
    exit

グローバルマクロの適切な設定を伴う必要があります。ここまでは順調ですね; スニペット

    global myvars mpg foreign
    do myreg

望ましい結果が得られます。ここで、非常に優れた回帰結果を生成すると主張する有名なdo-fileを共同編集者に電子メールで送信し、入力するように指示したとします。

    do myreg

彼らの協力者は何を見ますか?mpg最良の場合、 Stataの新しいインスタンスを開始したかどうかの平均と中央値(結合の失敗:myreg.do空でない変数リストでこれを実行するつもりであるとは本当に知りませんでした)。しかし、共同作業者が作業中に何かを持っていて、グローバルにmyvars定義された(名前の衝突)...人がいたら、それは惨事になるでしょう。

  1. グローバルは、次のようにディレクトリ名またはファイル名に使用されます。

    use $mydir\data1, clear
    

    神は何がロードされるかを知っているだけです。ただし、大規模なプロジェクトでは便利です。global mydirマスターdoファイルのどこかに定義したいと思うでしょう。

    global mydir `c(pwd)'
    
  2. グローバルは、コマンド全体のように、予測できないがらくたを格納するために使用できます。

    capture $RunThis
    

    神は何が実行されるかを知っているだけです。そうでないことを願いましょう! format c:\RunThisこれは暗黙的な強い結合の最悪のケースですが、意味のあるものが含まれるかどうかさえわからないため、そのcapture前にを置き、ゼロ以外の戻りコードを処理する準備をします_rc。(ただし、以下の私の例を参照してください。)

  3. Stata自身のグローバルの使用は、タイプIの過誤の確率/信頼水準のように、神の設定のためです。グローバル$S_levelは常に定義されます(もちろん、技術的には実行可能ですが、このグローバルを再定義するには、完全に馬鹿である必要があります)。ただし、これはほとんどの場合、バージョン5以下のコード(大まかに)のレガシー問題です。これは、脆弱性の低いシステム定数から同じ情報を取得できるためです。

    set level 90
    display $S_level
    display c(level)
    

ありがたいことに、グローバルはStataで非常に明示的であるため、デバッグと削除が簡単です。上記の状況のいくつか、そして確かに最初の状況では、do-file内でローカルと見なされるdo-fileにパラメーターを渡したいと思うでしょう`0'。ファイルでグローバルを使用する代わりに、myreg.doおそらく次のようにコーディングします

    unab varlist : `0'
    regress price `varlist'
    regress price `varlist', robust
    qreg    price `varlist'
    exit

これunabは保護の要素として機能します。入力が正当なvarlistでない場合、プログラムはエラーメッセージで停止します。

私が見た中で最悪のケースでは、グローバルは定義された後に一度だけ使用されました。

グローバルを使用したい場合があります。そうしないと、他のすべてのdoファイルまたはプログラムに血まみれのことを渡さなければならないからです。グローバル変数がかなり避けられないことがわかった1つの例は、最尤推定量をコーディングすることでした。この場合、方程式とパラメーターの数が事前にわかりませんでした。Stataは、(ユーザー提供の)尤度エバリュエーターには特定の方程式があると主張しています。そのため、方程式をグローバルに蓄積してから、Stataが解析する必要のある構文の説明でグローバルを使用してエバリュエーターを呼び出す必要がありました。

args lf $parameters

lf目的関数(対数尤度)はどこにありましたか。denormix通常の混合パッケージ( )と確認的因子分析パッケージ( )で、これに少なくとも2回遭遇しましたconfafinditもちろん、両方ともできます。

于 2011-08-02T19:02:13.470 に答える
12

意見を分割するグローバル変数の1つのRの例は、stringsAsFactorsデータをRに読み取るか、データフレームを作成する際の問題です。

set.seed(1)
str(data.frame(A = sample(LETTERS, 100, replace = TRUE),
               DATES = as.character(seq(Sys.Date(), length = 100, by = "days"))))
options("stringsAsFactors" = FALSE)
set.seed(1)
str(data.frame(A = sample(LETTERS, 100, replace = TRUE),
               DATES = as.character(seq(Sys.Date(), length = 100, by = "days"))))
options("stringsAsFactors" = TRUE) ## reset

オプションがRで実装される方法のため、これを実際に修正することはできません。知らないうちにオプションが変更される可能性があるため、同じコードチャンクがまったく同じオブジェクトを返すことは保証されません。John Chambersは、最近の本でこの機能を嘆いています。

于 2011-04-02T22:45:12.440 に答える
8

piRの病理学的例は、円の面積を計算するためにRで使用可能なグローバルの1つを使用することです。

> r <- 3
> pi * r^2
[1] 28.27433
> 
> pi <- 2
> pi * r^2
[1] 18
> 
> foo <- function(r) {
+     pi * r^2
+ }
> foo(r)
[1] 18
> 
> rm(pi)
> foo(r)
[1] 28.27433
> pi * r^2
[1] 28.27433

もちろん、foo()を強制的に使用することで関数を防御的に記述できますが、パッケージ化して:base::piを使用しない限り、このような手段は通常のユーザーコードでは使用できない場合があります。NAMESPACE

> foo <- function(r) {
+     base::pi * r^2
+ }
> foo(r = 3)
[1] 28.27433
> pi <- 2
> foo(r = 3)
[1] 28.27433
> rm(pi)

これは、関数のスコープ内にあるだけではなく、引数として明示的に渡されたものに依存することによって発生する可能性のある混乱を浮き彫りにします。

于 2011-04-04T08:35:28.380 に答える
8

これは、置換関数、グローバル割り当て、およびグローバルとローカルの両方で定義されたxを含む興味深い病理学的例です...

x <- c(1,NA,NA,NA,1,NA,1,NA)

local({

    #some other code involving some other x begin
    x <- c(NA,2,3,4)
    #some other code involving some other x end

    #now you want to replace NAs in the the global/parent frame x with 0s
    x[is.na(x)] <<- 0
})
x
[1]  0 NA NA NA  0 NA  1 NA

置換関数は、を返す代わりに、xのグローバル値に割り当てている場合でも、[1] 1 0 0 0 1 0 1 0のローカル値によって返されるインデックスを使用します。is.na(x)この動作は、R言語定義に記載されています。

于 2012-08-01T16:28:13.943 に答える
5

Rの簡単で説得力のある例の1つは、次のように行を実行することです。

.Random.seed <- 'normal'

私は誰かが選ぶかもしれないものとして「通常」を選びました、しかしあなたはそこで何でも使うことができました。

次に、生成された乱数を使用するコードを実行します。次に例を示します。

rnorm(10)

次に、どのグローバル変数でも同じことが起こる可能性があることを指摘できます。

次の例も使用します。

x <- 27
z <- somefunctionthatusesglobals(5)

次に、生徒にその価値を尋ねxます。答えはわからないということです。

于 2011-04-03T02:39:42.703 に答える
5

試行錯誤の結果、すべてを可能な限り堅牢にするために、関数の引数に名前を付ける際に非常に明示的にする必要があることを学びました(そして、関数の開始時と関数に沿って十分なチェックを行う必要があります)。これは、グローバル環境に変数が格納されている場合に特に当てはまりますが、カスタムの貴重品を使用して関数をデバッグしようとすると、何かが足りなくなります。これは、不正なチェックとグローバル変数の呼び出しを組み合わせた簡単な例です。

glob.arg <- "snake"
customFunction <- function(arg1) {
    if (is.numeric(arg1)) {
        glob.arg <- "elephant"
    }

    return(strsplit(glob.arg, "n"))
}

customFunction(arg1 = 1) #argument correct, expected results
customFunction(arg1 = "rubble") #works, but may have unexpected results
于 2011-04-03T05:03:24.517 に答える
3

今日これを教えようとしているときに出てきたスケッチの例。具体的には、グローバルが問題を引き起こす可能性がある理由を直感的に理解することに焦点を当てているため、コードだけで結論を出すことができるものとできないものを述べるために、可能な限り抽象化します(関数をブラックボックスのままにします)。

セットアップ

ここにいくつかのコードがあります。与えられた基準のみに基づいて、エラーを返すかどうかを決定します。

コード

stopifnot( all( x!=0 ) )
y <- f(x)
5/x

基準

ケース1:f()ローカル変数のみを使用する適切に動作する関数です。

ケース2:f()必ずしも適切に動作する関数ではなく、グローバル割り当てを使用する可能性があります。

答え

xケース1:1行目はゼロに等しい'がないことを確認し、3行目は。で除算するため、コードはエラーを返しませんx

ケース2:コードがエラーを返す可能性があります。たとえば、親環境でf()1を減算しxて割り当て、1に等しい要素をゼロに設定すると、3行目でゼロ除算が返される可能性があります。エラー。xx

于 2011-10-26T18:05:35.403 に答える
2

これは、統計タイプに意味のある答えの1つの試みです。

  • 名前空間の衝突:一般名(x、iなど)が再利用され、名前空間の衝突が発生します

まず、対数尤度関数を定義します。

logLik <- function(x) {
   y <<- x^2+2
   return(sum(sqrt(y+7)))
}

ここで、入力の2乗の合計を返す無関係の関数を記述します。怠惰なので、これを実行して、グローバル変数としてyを渡します。

sumSq <- function() {
   return(sum(y^2))
}

y <<- seq(5)
sumSq()
[1] 55

対数尤度関数は、引数を取り、値を返すことで、期待どおりに動作するようです。

> logLik(seq(12))
[1] 88.40761

しかし、他の機能はどうなっているのでしょうか。

> sumSq()
[1] 633538

もちろん、これは簡単な例であり、複雑なプログラムには存在しない例も同様です。しかし、うまくいけば、ローカルよりもグローバルを追跡することがどれほど難しいかについての議論が活発になるでしょう。

于 2011-04-04T16:36:21.873 に答える
0

Rでは、環境を変更するだけで関数自体のから関数スコープで定義された変数にアクセスできるため、グローバルを使用する必要がない場合が多いことを示すこともできます。たとえば、以下のコード

zz="aaa"
x = function(y) { 
     zz="bbb"
     cat("value of zz from within the function: \n")
     cat(zz , "\n")
     cat("value of zz from the function scope: \n")
     with(environment(x),cat(zz,"\n"))
}
于 2011-04-02T22:54:13.873 に答える