2010-04-13 18 views
96

Tôi vừa đọc xong khoảng scoping in the R intro và rất tò mò về bài tập <<-.Làm thế nào để bạn sử dụng "<< -" (phân bổ phạm vi) trong R?

Hướng dẫn hiển thị một ví dụ (rất thú vị) cho <<-, mà tôi cảm thấy tôi đã hiểu. Những gì tôi vẫn còn thiếu là bối cảnh khi điều này có thể hữu ích.

Vì vậy, những gì tôi muốn đọc từ bạn là ví dụ (hoặc liên kết đến các ví dụ) khi sử dụng <<- có thể thú vị/hữu ích. Điều gì có thể là mối nguy hiểm của việc sử dụng nó (có vẻ dễ dàng để theo dõi), và bất kỳ lời khuyên bạn có thể cảm thấy như chia sẻ.

Trả lời

139

<<- hữu ích nhất cùng với các bao đóng để duy trì trạng thái. Dưới đây là một phần từ một bài báo gần đây của tôi:

Đóng là một hàm được viết bởi một hàm khác.Các bao đóng được gọi như vậy bởi vì chúng đính kèm môi trường của hàm cha và có thể truy cập tất cả các biến và tham số trong hàm đó. Điều này rất hữu ích vì nó cho phép chúng ta có hai mức tham số. Một mức tham số (cha mẹ) kiểm soát cách hoạt động của hàm. Cấp độ khác (đứa trẻ) thực hiện công việc. Ví dụ sau đây cho thấy làm thế nào có thể sử dụng ý tưởng này để tạo ra một gia đình của các chức năng quyền lực. Hàm cha (power) tạo các hàm con (squarecube) thực sự làm công việc khó khăn.

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 

Khả năng quản lý biến ở hai cấp độ cũng có thể duy trì trạng thái qua các lần gọi hàm bằng cách cho phép sửa đổi các biến trong môi trường của cha mẹ. Chìa khóa để quản lý các biến ở các cấp khác nhau là toán tử gán mũi tên kép <<-. Không giống như việc gán mũi tên đơn thông thường (<-) luôn hoạt động ở cấp hiện tại, toán tử mũi tên đôi có thể sửa đổi các biến ở cấp độ gốc.

Điều này giúp duy trì bộ đếm ghi lại số lần hàm được gọi, như ví dụ sau đây. Mỗi lần new_counter được chạy, nó tạo ra một môi trường, khởi tạo bộ đếm i trong môi trường này và sau đó tạo một hàm mới.

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

Chức năng mới là đóng cửa và môi trường của nó là môi trường kèm theo. Khi đóng cửa counter_onecounter_two được chạy, mỗi lần sửa đổi bộ đếm trong môi trường kèm theo của nó và sau đó trả về số đếm hiện tại.

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

counter_one() # -> [1] 1 
counter_one() # -> [1] 2 
counter_two() # -> [1] 1 
+2

Đây là một nhiệm vụ R chưa được giải quyết trên Rosettacode (http://rosettacode.org/wiki/Accumulator_factory#R) Vâng, nó là ... –

+0

Sẽ có bất kỳ nhu cầu nào kèm theo nhiều hơn 1 đóng trong một hàm cha? Tôi vừa thử một đoạn, có vẻ như chỉ việc đóng cửa cuối cùng được thực hiện ... –

5

Một nơi mà tôi đã sử dụng <<- là trong GUI đơn giản sử dụng tcl/tk. Một số ví dụ ban đầu có nó - khi bạn cần tạo sự khác biệt giữa các biến cục bộ và toàn cục cho tính trạng thái. Xem ví dụ

library(tcltk) 
demo(tkdensity) 

sử dụng <<-. Nếu không, tôi đồng ý với Marek :) - một tìm kiếm của Google có thể giúp bạn.

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

Đây là ví dụ hay về nơi _không sử dụng '<< -'. Vòng lặp for sẽ rõ ràng hơn trong trường hợp này. – hadley

25

Nó giúp để nghĩ về <<- như tương đương với assign (nếu bạn thiết lập các thông số inherits trong chức năng đó để TRUE). Lợi ích của assign là nó cho phép bạn chỉ định nhiều thông số hơn (ví dụ: môi trường), vì vậy tôi muốn sử dụng assign hơn <<- trong hầu hết các trường hợp.

Sử dụng <<-assign(x, value, inherits=TRUE) có nghĩa là "môi trường kèm theo của môi trường được cung cấp được tìm kiếm cho đến khi biến 'x' gặp phải." Nói cách khác, nó sẽ tiếp tục đi qua các môi trường theo thứ tự cho đến khi nó tìm thấy một biến với tên đó, và nó sẽ gán nó cho điều đó. Điều này có thể nằm trong phạm vi của một hàm, hoặc trong môi trường toàn cục.

Để hiểu những chức năng này làm gì, bạn cũng cần hiểu các môi trường R (ví dụ: sử dụng search).

Tôi thường xuyên sử dụng các chức năng này khi tôi đang chạy mô phỏng lớn và tôi muốn lưu kết quả trung gian. Điều này cho phép bạn tạo đối tượng nằm ngoài phạm vi của hàm đã cho hoặc vòng lặp apply. Điều đó rất hữu ích, đặc biệt nếu bạn có bất kỳ mối quan tâm nào về một vòng lặp lớn kết thúc bất ngờ (ví dụ: ngắt kết nối cơ sở dữ liệu), trong trường hợp đó bạn có thể mất mọi thứ trong quá trình. Điều này sẽ tương đương với việc viết kết quả của bạn ra một cơ sở dữ liệu hoặc tệp trong một quá trình chạy dài, ngoại trừ việc nó lưu trữ kết quả trong môi trường R thay thế.

Cảnh báo chính của tôi với điều này: hãy cẩn thận vì bạn hiện đang làm việc với các biến toàn cầu, đặc biệt là khi sử dụng <<-. Điều đó có nghĩa là bạn có thể kết thúc với các tình huống mà một hàm đang sử dụng một giá trị đối tượng từ môi trường, khi bạn mong đợi nó sẽ sử dụng một hàm được cung cấp dưới dạng tham số. Đây là một trong những điều chính mà lập trình chức năng cố gắng tránh (xem side effects). Tôi tránh vấn đề này bằng cách gán giá trị của tôi cho một tên biến duy nhất (sử dụng dán với một tập hợp hoặc tham số duy nhất) không bao giờ được sử dụng trong hàm, nhưng chỉ được sử dụng cho bộ nhớ đệm và trong trường hợp tôi cần khôi phục sau này (hoặc làm một số meta -analysis trên các kết quả trung gian).

+2

Cảm ơn Tal. Tôi có một blog, mặc dù tôi không thực sự sử dụng nó. Tôi không bao giờ có thể hoàn thành một bài viết vì tôi không muốn xuất bản bất cứ điều gì trừ khi nó hoàn hảo, và tôi chỉ không có thời gian cho điều đó ... – Shane

+1

Một người khôn ngoan đã từng nói với tôi điều quan trọng không phải là hoàn hảo - chỉ đứng ngoài - bạn là ai, và các bài viết của bạn cũng vậy. Ngoài ra - đôi khi người đọc giúp cải thiện văn bản bằng các nhận xét (đó là điều xảy ra với blog của tôi). Tôi hy vọng một ngày nào đó bạn sẽ cân nhắc lại :) –

2

Với chủ đề này, tôi muốn chỉ ra rằng các nhà điều hành < < sẽ có biểu hiện lạ khi áp dụng (không chính xác) trong một vòng lặp for (có thể có trường hợp khác nữa). Căn cứ vào đoạn mã sau:

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

bạn có thể mong đợi rằng chức năng sẽ trả lại số tiền dự kiến, 6, nhưng thay vào đó nó sẽ trả về 0, với một biến toàn cầu mySum được tạo ra và gán giá trị 3. Tôi có thể không đầy đủ giải thích những gì đang xảy ra ở đây nhưng chắc chắn cơ thể của vòng lặp for là không phải là phạm vi mới 'cấp'. Thay vào đó, có vẻ như R nhìn bên ngoài chức năng fortest, không thể tìm thấy biến số mySum để gán cho, do đó hãy tạo một và gán giá trị 1, lần đầu tiên thông qua vòng lặp. Trong các lần lặp tiếp theo, RHS trong nhiệm vụ phải đề cập đến biến số bên trong (không đổi) mySum trong khi LHS đề cập đến biến toàn cầu. Do đó, mỗi lần lặp sẽ ghi đè giá trị của biến toàn cầu với giá trị của vòng lặp đó là i, do đó nó có giá trị 3 khi thoát khỏi hàm.

Hy vọng điều này sẽ giúp một người nào đó - điều này làm tôi bối rối trong một vài giờ ngay hôm nay! (BTW, chỉ cần thay thế < < bằng < và chức năng hoạt động như mong đợi).

+2

trong ví dụ của bạn, 'mySum'is cục bộ không bao giờ tăng lên mà chỉ là' mySum' toàn cầu. Do đó tại mỗi lần lặp của vòng lặp for, 'mySum' toàn cục nhận giá trị' 0 + i'. Bạn có thể làm theo điều này với 'debug (fortest)'. – clemlaflemme

+0

Nó không có gì để làm với nó là một vòng lặp; bạn đang tham khảo hai phạm vi khác nhau. Chỉ cần sử dụng '<-' ở mọi nơi nhất quán trong hàm nếu bạn chỉ muốn cập nhật biến cục bộ bên trong hàm. – smci

+0

Hoặc sử dụng << - everywhere @smci. Mặc dù tốt nhất để tránh globals. –

2

Nhà điều hành <<- cũng có thể hữu ích cho Reference Classes when writing Reference Methods. Ví dụ:

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