2011-12-09 36 views
16

Các mã sau đây không làm việc như tôi mong đợi:Hành vi lạ lùng: những gì tôi đã bỏ lỡ?

a <- list(0, 1) 
b <- list(0, 1) 

# return a linear function with slope `a` and intercept `b`. 
f <- function(a, b) function(x) a*x + b 

# create a list of functions with different parameters. 
fs <- mapply(f, a, b) 

# test 
fs[[1]](3) 
# [1] 4 # expected zero! 
fs[[2]](3) 
# [1] 4 

bất cứ ai có thể cho tôi biết tại sao?

NB: Tôi đã tìm được giải pháp thay thế, vì vậy tôi không tìm cách khác để đạt được kết quả mong muốn. Nhưng tôi tò mò là tại sao cách tiếp cận đặc biệt này không hiệu quả.


Cập nhật:

Tính R 3.2.0, điều này hiện đang làm việc như mong đợi:

a <- list(0, 1) 
b <- list(0, 1) 
f <- function(a, b) function(x) a*x + b 
fs <- mapply(f, a, b) 

# test 
fs[[1]](3) 
# [1] 0 
fs[[2]](3) 
# [1] 4 
+0

+1 để tìm hành vi kỳ lạ này! Tôi yêu những điều này :-) – Tommy

+0

Có thể không phải là một điều * áp dụng: 'fs <- list(); cho (i trong 1: 2) fs [[i]] <- f (a [[i]], b [[i]]) 'làm điều tương tự. – pete

Trả lời

8

[Cập nhật] phân tích ban đầu của tôi là đúng nhưng kết luận là sai :) Hãy đi đến kết luận sau khi phân tích.

Dưới đây là một số mã chứng minh tác dụng:

x <- lapply(1:3, function(x) sys.frame(sys.nframe())) 
x[[1]] # An environment 
x[[2]] # Another environment 
x[[3]] # Yet nother environment 
x[[1]]$x # 3!!! (should be 1) 
x[[2]]$x # 3!! (should be 2) 
x[[3]]$x # 3 as expected 

# Accessing the variable within the function will "fix" the weird behavior: 
x <- lapply(1:3, function(x) {x; sys.frame(sys.nframe())}) 
x[[1]]$x # 1 
x[[2]]$x # 2 
x[[3]]$x # 3 

Vì vậy, các công việc xung quanh trong trường hợp của bạn:

f <- function(a, b) { a;b; function(x) a*x + b } 

Btw, như @ James ghi nhận có một chức năng force mà làm cho truy cập vào một biến rõ ràng hơn:

f <- function(a, b) { force(a);force(b); function(x) a*x + b } 

Conclu sions

Vâng, như @mbq và @hadley lưu ý, điều này là do đánh giá lười biếng. Nó dễ dàng hơn để hiển thị với một đơn giản cho vòng lặp:

fs <- list(); for(i in 1:2) fs[[i]] <- f(a[[i]], b[[i]]) 

Chức năng f 'x luận s sẽ không nhận được giá trị của a[[i]] (đó là 0), nhưng toàn bộ biểu và môi trường có ai tồn tại. Khi bạn truy cập x, nó được đánh giá và do đó sử dụng i tại thời điểm đánh giá. Nếu vòng lặp for đã di chuyển kể từ khi gọi tới số f, bạn nhận được kết quả "sai" ...

Ban đầu tôi đã nói rằng điều này là do lỗi trong *apply, không phải vậy. ... nhưng kể từ khi tôi ghét phải là sai, tôi có thể chỉ ra rằng * Áp dụng KHÔNG có một lỗi (hoặc có lẽ nhiều hơn một sự mâu thuẫn) trong những trường hợp:

lapply(11:12, function(x) sys.call()) 
#[[1]] 
#FUN(11:12[[1L]], ...) 
# 
#[[2]] 
#FUN(11:12[[2L]], ...) 

lapply(11:12, function(x) function() x)[[1]]() # 12 
lapply(11:12, function(x) function() x)[[2]]() # 12 

Như bạn thấy ở trên, mã lapplynói, nó gọi hàm là 11:12[[1L]]. Nếu bạn đánh giá rằng "sau" bạn nên vẫn nhận được giá trị 11 - nhưng bạn thực sự nhận được 12!

Đây là lẽ do thực tế rằng lapply được thực hiện trong mã C vì lý do hiệu suất và ăn gian một chút, vì vậy biểu thức mà nó cho thấy không phải là sự biểu hiện đó được đánh giá - ergo, một lỗi ...

QED

+0

Xem '? Force' cho một ví dụ tương tự – James

+4

Nó không phải là lỗi. Đó là hậu quả của việc đánh giá lười biếng. – hadley

+0

@hadley - Tôi đã sửa. Tôi đã cập nhật câu trả lời của mình. – Tommy

9

Đây là kết quả của việc đánh giá lười biếng - tất cả các đối số được chuyển xuống cây gọi để tránh thực thi không cần thiết và vẫn ở trạng thái tạm ngưng này cho đến khi R được thuyết phục rằng chúng được sử dụng.

Trong mã của bạn, bạn chỉ điền các hàm có cùng lời hứa với một lời hứa tương tự cho b; thì tất cả họ đều cam kết với một cặp vales cuối cùng. Như @Tommy đã cho thấy, giải pháp là buộc cam kết bằng cách "sử dụng" giá trị trước khi hàm được xác định.