2013-04-21 17 views
57

Czytam książkę Hadley Wickhams o Github, w szczególności this part on lazy evaluation. Podaje on przykład konsekwencji leniwej oceny w części z funkcjami add/adders. Zacytuję że nieco:Wyjaśnij leniwe zdanie oceny

Ten [leniwa ewaluacja] jest ważne przy tworzeniu zamknięć z lapply lub pętlę:

add <- function(x) { 
    function(y) x + y 
} 
adders <- lapply(1:10, add) 
adders[[1]](10) 
adders[[10]](10) 

x jest leniwie oceniany po raz pierwszy, że nazywasz jeden z sumatora Funkcje. W tym momencie pętla jest kompletna, a końcowa wartość x wynosi 10. Dlatego wszystkie funkcje sumatora dodają 10 na ich wejście , prawdopodobnie nie to, co chciałeś! Ręczne wymuszenie poprawki oceny problem:

add <- function(x) { 
    force(x) 
    function(y) x + y 
} 
adders2 <- lapply(1:10, add) 
adders2[[1]](10) 
adders2[[10]](10) 

I nie wydają się zrozumieć ten kawałek, a wyjaśnienie jest minimalne. Czy ktoś mógłby rozwinąć ten konkretny przykład i wyjaśnić, co się tam dzieje? Jestem szczególnie zaskoczony zdaniem "w tym momencie pętla jest kompletna, a ostateczna wartość x wynosi 10". Jaka pętla? Jaką ostateczną wartość, gdzie? Muszę być czymś prostym, którego mi brakuje, ale po prostu tego nie widzę. Z góry dziękuję.

+3

Należy zauważyć, że odpowiedź na to pytanie nie zmieniło jak R 3.2.0, patrz poniżej moją odpowiedź. – jhin

+0

Uzupełnienie komentarza @ jhina: Podczas gdy 'lapply()' zmieniło się w ostatnim R, funkcja 'purrr :: map()', która ma być używana wszędzie tam, gdzie jest 'lapply()', nadal zachowuje się jak stary ' lapply() "w stosunku do wspólnych środowisk zamknięć. Nie liczyłbym jednak na ten "anachronizm" "purrr :: map()", który zostanie prawdopodobnie usunięty w przyszłych wersjach. – egnha

+0

@jhin Właściwie, tutorial hadley'a jest zbudowany bezpośrednio z github, więc czytanie go po R 3.2.0 jest teraz dość dziwne, ponieważ to wydanie stworzyło całą sekcję o leniwej ocenie w tym tutorialowym sporze: nie ma więcej różnicy z 'adders' i 'wyjścia adders2'! –

Odpowiedz

34

Cel:

adders <- lapply(1:10, function(x) add(x)) 

jest stworzenie listy add funkcji, pierwszy dodaje 1 do jego wejścia, drugi dodaje 2 itd ocena Lazy powoduje R czekać na stworzenie naprawdę adders działa, dopóki naprawdę nie zaczniesz wywoływać funkcji. Problem polega na tym, że po utworzeniu pierwszej funkcji sumatora, x jest zwiększany przez pętlę lapply, kończącą się na wartości 10. Gdy wywołujesz funkcję pierwszego sumatora, leniwe obliczenia budują obecnie funkcję, uzyskując wartość x. Problemem jest to, że nie jest już oryginalny x równa jest jeden, ale do wartości na koniec pętli lapply, czyli 10.

Dlatego ocena leniwy powoduje, że wszystkie funkcje dodatków, poczekać, aż po pętli lapply zakończyła w naprawdę budowaniu funkcji. Następnie budują swoją funkcję o tej samej wartości, tj. 10. Rozwiązanie, które sugeruje Hadley, to wymuszenie bezpośredniej oceny x, unikanie leniwej oceny i uzyskiwanie prawidłowych funkcji z poprawnymi wartościami x.

+4

Ok, pozwól mi zmienić to, aby sprawdzić, czy rozumiem. Kiedy nazywamy 'lapply', R rodzaj zapamiętuje strukturę wszystkich 10 funkcji sumatora, ale nie ocenia jeszcze x. Kiedy wywołujemy pierwszą funkcję sumatora, R mówi, aha, zobaczmy, co to jest, bierze x, który już jest 10 w tym punkcie od lapply call i ocenia pierwszą zwaną funkcję adder jako 10 + y. To samo dotyczy pozostałych funkcji sumatora, czyniąc je identycznymi. Prawdopodobnie nieostrożnie, ale czy to logika? –

+0

Uważam, że tak jest. –

+1

@ Maxim.K tak, to jest dokładnie w porządku. – hadley

52

To nie jest już prawdą z R 3.2.0!

odpowiedniej linii w change log brzmi:

funkcja wyższego rzędu, takich jak zastosowanie funkcji i Reduce() teraz argumenty siły do ​​funkcji, jakie są stosowane w celu wyeliminowania niepożądane interakcje między leniwe oceny i zmienne przechwytywanie w zamknięciach.

I rzeczywiście:

add <- function(x) { 
    function(y) x + y 
} 
adders <- lapply(1:10, add) 
adders[[1]](10) 
# [1] 11 
adders[[10]](10) 
# [1] 20 
+1

Świetna informacja. Zaskoczył mnie również i pomyślałem, że mógł to zrobić lapidarnie – AllYouCanEat86