Làm thế nào để bạn sử dụng ngôn ngữ << - (phạm vi phân bổ) trong R?


140

Tôi vừa đọc xong về phạm vi trong phần giới thiệu R , và tôi rất tò mò về <<-bài tập này.

Hướng dẫn cho thấy 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à các ví dụ (hoặc liên kết đến các ví dụ) khi việc sử dụng <<-có thể thú vị / hữu ích. Điều gì có thể là sự nguy hiểm của việc sử dụng nó (có vẻ dễ dàng để mất dấu vết) và bất kỳ mẹo nào bạn có thể cảm thấy muốn chia sẻ.

Câu trả lời:


196

<<-là hữu ích nhất kết hợp với đóng cửa để duy trì trạng thái. Đây là một phần trong bài báo gần đây của tôi:

Một bao đóng là một chức năng được viết bởi một chức năng khác. Các bao đóng được gọi như vậy vì chúng bao quanh 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 cấp độ tham số. Một mức tham số (cha) kiểm soát cách thức hoạt động của chức năng. Các cấp độ khác (trẻ em) làm 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 họ các hàm năng lượng. Hàm cha ( power) tạo ra 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ý các biến ở hai cấp độ cũng giúp duy trì trạng thái trên các lệnh gọi hàm bằng cách cho phép một hàm sửa đổi các biến trong môi trường của cha mẹ của nó. 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ư phép 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 kép có thể sửa đổi các biến ở cấp độ cha.

Điều này cho phép duy trì bộ đếm ghi lại số lần hàm được gọi, như ví dụ sau đây cho thấy. Mỗi lần new_counterchạy, nó tạo ra một môi trường, khởi tạo bộ đếm itrong 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
  }
}

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

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

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

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

1
Có cần phải bao gồm nhiều hơn 1 lần đóng trong một hàm cha không? Tôi mới thử một đoạn, dường như chỉ có lần đóng cuối cùng được thực thi ...
mckf111

Có bất kỳ dấu bằng nào thay thế cho dấu "<< -" không?
Genom

38

Nó giúp suy nghĩ <<-tương đương với assign(nếu bạn đặt inheritstham số trong hàm đó thành TRUE). Lợi ích của assignnó là cho phép bạn chỉ định nhiều tham số hơn (ví dụ như môi trường), vì vậy tôi thích sử dụng assignhơ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 gặp biến 'x'." 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 tìm thấy một biến có tên đó và nó sẽ gán nó cho nó. Điều này có thể nằm trong phạm vi của một chức năng hoặc trong môi trường toàn cầu.

Để hiểu những chức năng này làm gì, bạn cũng cần hiểu 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ột 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 bên ngoài phạm vi của hàm hoặc applyvòng lặp đã cho. Điều đó rất hữu ích, đặc biệt nếu bạn có bất kỳ lo ngại 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 quy trình. Điều này sẽ tương đương với việc ghi kết quả của bạn ra 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.

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ục, đặ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 trong đó một hàm đang sử dụng một giá trị đối tượng từ môi trường, khi bạn dự kiến ​​nó sẽ sử dụng một tình huống được cung cấp như một 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 tác dụng phụ ). Tôi tránh vấn đề này bằng cách gán giá trị của mình cho một tên biến duy nhất (sử dụng dán với một tham số hoặc tập hợp duy nhất) không bao giờ được sử dụng trong hàm, nhưng chỉ được sử dụng cho bộ đệm và trong trường hợp tôi cần khôi phục sau này (hoặc thực hiện một số meta -Phân tích kết quả trung gian).


3
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 không có thời gian cho việc đó ...
Shane

2
Một người đàn ông khôn ngoan đã từng nói với tôi rằng điều đó không quan trọng để trở nên hoàn hảo - chỉ đứng ngoài cuộc - chính là bạn, và 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 với các bình luận (đó là những gì xảy ra với blog của tôi). Tôi hy vọng một ngày nào đó bạn sẽ xem xét lại :)
Tal Galili

9

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

 library(tcltk)
 demo(tkdensity)

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


Thật thú vị, bằng cách nào đó tôi không thể tìm thấy tkdensitytrong R 3.6.0.
NelsonGon


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

11
Đây là một ví dụ tốt về nơi không sử dụng <<-. Một vòng lặp for sẽ rõ ràng hơn trong trường hợp này.
hadley

4

Về chủ đề này, tôi muốn chỉ ra rằng <<-toán tử sẽ hành xử kỳ lạ khi được áp dụng (không chính xác) trong vòng lặp for (cũng có thể có các trường hợp khác). Cho 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 hàm sẽ trả về tổng số dự kiến ​​là 6, nhưng thay vào đó, nó trả về 0, với một biến toàn cục mySumđược tạo và gán giá trị 3. Tôi không thể giải thích đầy đủ những gì đang diễn ra ở đây nhưng chắc chắn là phần thân của vòng lặp không phải là một phạm vi mới 'cấp độ'. Thay vào đó, có vẻ như R nhìn bên ngoài fortesthàm, không thể tìm thấy một mySumbiến để gán, vì vậy 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 lại tiếp theo, RHS trong bài tập phải được đề cập đến mySumbiến bên trong (không thay đổi) trong khi LHS đề cập đến biến toàn cục. Do đó, mỗi lần lặp lại ghi đè giá trị của biến toàn cục thành giá trị của lần lặp iđó, do đó, nó có giá trị 3 khi thoát khỏi hàm.

Hy vọng điều này sẽ giúp được ai đó - điều này làm tôi bối rối trong vài giờ 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, địa phương mySumkhông bao giờ tăng mà chỉ toàn cầu mySum. Do đó tại mỗi lần lặp của vòng lặp for, toàn cầu mySumnhận được giá trị 0 + i. Bạn có thể làm theo điều này với debug(fortest).
ClementWalter

Không có gì để làm với nó là một vòng lặp for; bạn đang tham khảo hai phạm vi khác nhau. Chỉ cần sử dụng <-ở mọi nơi một cách 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

Hoặc sử dụng << - ở mọi nơi @smci. Mặc dù tốt nhất để tránh toàn cầu.
Học thống kê bằng ví dụ

3

Các <<-nhà điều hành cũng có thể hữu ích cho lớp tham khảo khi viết Phương pháp tham khảo . 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.