web-dev-qa-db-ja.com

Rで "<<-"(スコープの割り当て)をどのように使用しますか?

Rイントロのスコープ について読み終え​​たばかりで、<<-割り当てについて非常に興味があります。

マニュアルには、<<-の1つの(非常に興味深い)例が示されていました。私がまだ行方不明になっているのは、これがいつ役に立つかという文脈です。

したがって、あなたから読みたいのは、<<-の使用が興味深い/有用な場合の例(または例へのリンク)です。それを使用することの危険性は何でしょうか(追跡が簡単に見えます)、および共有したいと思うかもしれないあらゆるヒント。

118
Tal Galili

<<-は、状態を維持するためにクロージャーと併用すると最も便利です。これが私の最近の論文の一部です。

クロージャは、別の関数によって記述された関数です。 enclose親関数の環境であり、その関数内のすべての変数とパラメーターにアクセスできるため、クロージャーはそう呼ばれます。これは、2つのレベルのパラメーターを持つことができるため便利です。 1つのレベルのパラメーター(親)は、関数の動作を制御します。他のレベル(子)が作業を行います。次の例は、この考え方を使用してべき関数のファミリーを生成する方法を示しています。親関数(power)は、実際にハードワークを行う子関数(squareおよびcube)を作成します。

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

また、2つのレベルで変数を管理する機能により、関数が親の環境内の変数を変更できるようにすることで、関数呼び出し間で状態を維持できます。さまざまなレベルで変数を管理する鍵は、二重矢印割り当て演算子<<-。通常の単一矢印の割り当て(<-)常に現在のレベルで機能します。二重矢印演算子は、親レベルの変数を変更できます。

これにより、次の例に示すように、関数が呼び出された回数を記録するカウンターを維持できます。毎回 new_counterが実行され、環境が作成され、この環境でカウンターiが初期化されてから、新しい関数が作成されます。

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

新しい関数はクロージャーであり、その環境はエンクロージング環境です。クロージャcounter_oneおよびcounter_twoが実行され、それぞれがそれを囲む環境でカウンターを変更し、現在のカウントを返します。

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
167
hadley

_<<-_をassignと同等と考えると役立ちます(その関数のinheritsパラメーターをTRUEに設定した場合)。 assignの利点は、より多くのパラメーター(環境など)を指定できることです。そのため、ほとんどの場合、_<<-_よりもassignを使用することを好みます。

_<<-_およびassign(x, value, inherits=TRUE)を使用することは、「指定された環境の囲まれた環境が、変数 'x'に遭遇するまで検索される」ことを意味します。つまり、その名前の変数が見つかるまで環境を順番に調べ、その変数に割り当てます。これは、機能の範囲内でも、グローバル環境でも可能です。

これらの関数が何をするのかを理解するには、R環境も理解する必要があります(たとえば、searchを使用)。

大規模なシミュレーションを実行しているときにこれらの関数を定期的に使用し、中間結果を保存したい。これにより、特定の関数またはapplyループのスコープ外でオブジェクトを作成できます。特に、予期しない大きなループの終了(データベースの切断など)について懸念がある場合は非常に役立ちます。この場合、プロセスのすべてが失われる可能性があります。これは、結果をR環境内に保存することを除いて、長時間実行プロセス中にデータベースまたはファイルに結果を書き込むことと同等です。

これに関する私の第一の警告:特に_<<-_を使用している場合は、グローバル変数を使用しているので注意してください。つまり、関数が環境のオブジェクト値を使用している場合に、パラメーターとして提供されたオブジェクト値を使用すると予想される状況に陥ることがあります。これは、関数型プログラミングが回避しようとする主なものの1つです( 副作用 を参照)。この問題を回避するには、値を関数内で決して使用されない一意の変数名(セットまたは一意のパラメーターを使用して貼り付け)に割り当てます。 -中間結果の分析)。

33
Shane

<<-を使用した場所の1つは、tcl/tkを使用したシンプルなGUIでした。最初の例のいくつかには、ステートフルネスのためにローカル変数とグローバル変数を区別する必要があるため、それがあります。たとえば

 library(tcltk)
 demo(tkdensity)

<<-を使用します。そうでなければ、マレックに同意します:)-Google検索が役立ちます。

7
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
4
lcgong

このテーマについて、<<-演算子は、forループ内で(誤って)適用された場合、奇妙な動作をします(他の場合もあります)。次のコードを考えます:

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

関数が期待される合計6を返すことを期待するかもしれませんが、代わりに0を返します。グローバル変数mySumが作成され、値3が割り当てられます。確かにforループの本体はnot新しいスコープ「レベル」です。代わりに、Rはfortest関数の外側に見え、mySum変数を見つけることができないため、ループを最初に作成して値1を割り当てます。後続の反復では、割り当てのRHSは(変更されていない)内部mySum変数を参照する必要がありますが、LHSはグローバル変数を参照します。したがって、各反復はグローバル変数の値をその反復のiの値に上書きするため、関数の終了時に値3になります。

これが誰かを助けることを願っています-これは今日数時間私を困惑させました! (ところで、単に<<- with <-および関数は期待どおりに機能します)。

2
Matthew Wise

<<-演算子は、 参照メソッドを記述するときの参照クラス にも役立ちます。例えば:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9
2
Carlos Cinelli