Jakiś czas temu zostałem upomniany przez Simona Urbanka z zespołu podstawowego R (jak sądzę) za zalecenie użytkownikowi jawnego wywołania return
na końcu funkcji (jego komentarz został jednak usunięty):
foo = function() {
return(value)
}
zamiast tego zalecił:
foo = function() {
value
}
Prawdopodobnie w takiej sytuacji jest to wymagane:
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
Jego komentarz rzucił nieco światła na to, dlaczego nie dzwonić, return
chyba że jest to absolutnie potrzebne, ale jest to dobre.
Moje pytanie brzmi: dlaczego nie dzwonisz return
szybciej ani lepiej, a zatem lepiej?
return
jest niepotrzebny nawet w ostatnim przykładzie. Usunięciereturn
może trochę przyspieszyć, ale moim zdaniem dzieje się tak, ponieważ mówi się, że R jest funkcjonalnym językiem programowania.return
wywołuje skok nielokalny, a wyraźny skok nielokalny jest niezwykły dla FP. W rzeczywistości na przykład schemat nie mareturn
. Myślę, że moje komentarze są zbyt krótkie (i być może niepoprawne) jako odpowiedź.return
,break
,continue
albo, co jest nudny czasem.Odpowiedzi:
Pytanie brzmiało: dlaczego (jawnie) wywołanie powrotu nie jest szybsze ani lepsze, a zatem preferowane?
W dokumentacji R nie ma takiego stwierdzenia.
Strona główna? „Funkcja” mówi:
Czy jest to szybsze bez konieczności powrotu?
Zarówno
function()
ireturn()
są funkcjami prymitywne ifunction()
sama zwraca ostatnią wartość oceniana nawet bez włączaniareturn()
funkcji.Wywołanie
return()
jak.Primitive('return')
z tą ostatnią wartością jako argumentem wykona tę samą pracę, ale wymaga jeszcze jednego połączenia. Aby to (często) niepotrzebne.Primitive('return')
połączenie mogło przyciągnąć dodatkowe zasoby. Prosty pomiar pokazuje jednak, że wynikowa różnica jest bardzo mała, a zatem nie może być powodem nieużywania wyraźnego zwrotu. Z danych wybranych w ten sposób tworzony jest następujący wykres:Zdjęcie powyżej może nieznacznie różnić się na twojej platformie. Na podstawie zmierzonych danych rozmiar zwracanego obiektu nie powoduje żadnej różnicy, liczba powtórzeń (nawet jeśli jest skalowana w górę) stanowi tylko bardzo małą różnicę, która w prawdziwym słowie z prawdziwymi danymi i prawdziwym algorytmem nie mogła być policzona skrypt działa szybciej.
Czy to lepiej bez dzwonienia do powrotu?
Return
jest dobrym narzędziem do wyraźnego projektowania „liści” kodu, w których procedura powinna się zakończyć, wyskoczyć z funkcji i zwrócić wartość.Zależy od strategii i stylu programowania programisty, jakiego stylu używa, nie może on używać return (), ponieważ nie jest to wymagane.
Podstawowi programiści używają obu podejść, tj. z wyraźnym return () i bez niego, ponieważ możliwe jest znalezienie w źródłach funkcji „base”.
Wiele razy używana jest tylko return () (bez argumentu) zwracająca NULL w przypadkach warunkowo zatrzymujących funkcję.
Nie jest jasne, czy jest to lepsze czy nie, ponieważ zwykły użytkownik lub analityk używający R nie widzi prawdziwej różnicy.
Moim zdaniem pytanie powinno brzmieć: Czy istnieje jakiekolwiek niebezpieczeństwo w przypadku korzystania z wyraźnego zwrotu pochodzącego z wdrożenia R?
A może lepiej, że użytkownik piszący kod funkcji powinien zawsze pytać: Jaki jest efekt nieużywania jawnego powrotu (lub umieszczania obiektu, który ma zostać zwrócony jako ostatni liść gałęzi kodu) w kodzie funkcji?
źródło
return
i sprowadza się to do preferencji programisty, czy z niego korzystać, czy nie.return
jest naprawdę ostatnią rzeczą, o którą powinieneś się martwić.return
wywołań funkcji. Pytanie, które powinieneś zadać, nie jest tym, które zaproponujesz na końcu. Zamiast tego brzmi: „dlaczego powinienem używać redundancjireturn
? Jakie to zapewnia? ” Jak się okazuje, odpowiedź brzmi „niewiele”, a nawet „w ogóle”. Twoja odpowiedź nie docenia tego.return
jak wyraźny komentarz z napisem „zwiększ x o 1” obok fragmentu kodux = x + 2
. Innymi słowy, jego jawność jest (a) całkowicie nieistotna i (b) przekazuje błędne informacje. Ponieważreturn
semantyka w R to po prostu „porzucenie tej funkcji”. To nie znaczy tak samo jakreturn
w innych językach.Jeśli wszyscy się zgodzą
return
nie jest konieczne na końcu ciała funkcjireturn
jest nieznacznie szybsze (zgodnie z testem @ Alana, 4,3 mikrosekundy w porównaniu z 5.1)czy wszyscy powinniśmy przestać używać
return
na końcu funkcji? Na pewno nie i chciałbym wyjaśnić dlaczego. Mam nadzieję usłyszeć, czy inni ludzie podzielają moje zdanie. I przepraszam, jeśli nie jest to prosta odpowiedź na OP, ale bardziej jak długi subiektywny komentarz.Mój główny problem z nieużywaniem
return
polega na tym, że, jak zauważył Paul, istnieją inne miejsca w ciele funkcji, w których możesz ich potrzebować. A jeśli jesteś zmuszony użyćreturn
gdzieś pośrodku swojej funkcji, dlaczego nie wyrazisz wszystkichreturn
instrukcji? Nienawidzę być niekonsekwentnego. Myślę też, że kod czyta się lepiej; można zeskanować funkcję i łatwo zobaczyć wszystkie punkty wyjścia i wartości.Paul użył tego przykładu:
Niestety można zauważyć, że można go łatwo przepisać jako:
Ta ostatnia wersja jest nawet zgodna z niektórymi standardami kodowania programowania, które zalecają jedną instrukcję zwrotną na funkcję. Myślę, że lepszym przykładem może być:
Byłoby o wiele trudniej przepisać przy użyciu pojedynczej instrukcji return: potrzebowałoby wielu
break
s oraz skomplikowanego systemu zmiennych logicznych do ich propagowania. Wszystko po to, by powiedzieć, że reguła pojedynczego powrotu nie działa dobrze z R. Więc jeśli chcesz użyćreturn
w niektórych miejscach ciała swojej funkcji, dlaczego nie zachować spójności i używać jej wszędzie?Nie sądzę, aby argument prędkości był prawidłowy. Różnica 0,8 mikrosekundy jest niczym, gdy zaczynasz patrzeć na funkcje, które faktycznie coś robią. Ostatnią rzeczą, jaką widzę, jest to, że mniej się pisze, ale hej, nie jestem leniwy.
źródło
return
w niektórych przypadkach istnieje wyraźna potrzeba oświadczenia, jak pokazał @flodel. Alternatywnie istnieją sytuacje, w których najlepiej jest pominąć instrukcję return, np. Wiele wywołań małych funkcji. We wszystkich innych, powiedzmy 95% przypadków, tak naprawdę nie ma znaczenia, czy ktoś korzysta,return
czy nie, i sprowadza się to do preferencji. Lubię używać return, ponieważ jest ono bardziej wyraźne w tym, co masz na myśli, a zatem bardziej czytelne. Może ta dyskusja jest podobna do<-
vs=
?return
zwracanie wartości jest bezsensowne, na równi z pisaniemif (x == TRUE)
zamiastif (x)
.foo
jakofoo <- function(x) if (a) a else b
(z podziałami linii, jeśli to konieczne). Nie ma potrzeby wyraźnego zwrotu ani wartości pośredniej.To ciekawa dyskusja. Myślę, że przykład @ flodel jest doskonały. Myślę jednak, że to ilustruje mój punkt (i @koshke wspomina o tym w komentarzu), który
return
ma sens, gdy używasz trybu rozkazującego zamiast funkcjonalnego stylu kodowania .Nie chcę się nad tym zastanawiać, ale napisałbym
foo
tak:Funkcjonalny styl pozwala uniknąć zmian stanu, takich jak przechowywanie wartości
output
. W tym stylureturn
jest nie na miejscu;foo
wygląda bardziej jak funkcja matematyczna.Zgadzam się z @flodel: użycie skomplikowanego systemu zmiennych boolowskich w
bar
byłoby mniej jasne i bezcelowe, jeśli maszreturn
. To, co czynibar
tak podatnymi nareturn
oświadczenia, polega na tym, że jest napisane w trybie rozkazującym. Rzeczywiście, zmienne logiczne reprezentują zmiany „stanu”, których uniknięto w stylu funkcjonalnym.Naprawdę trudno jest przepisać
bar
w funkcjonalnym stylu, ponieważ jest to tylko pseudokod, ale pomysł jest mniej więcej taki:while
Pętla byłaby najtrudniejsza przepisać, ponieważ jest kontrolowane przez państwo do zmiana
.Utrata prędkości spowodowana wywołaniem
return
jest znikoma, ale wydajność uzyskana dzięki unikaniureturn
i przepisywaniu w funkcjonalnym stylu jest często ogromna. Powiedzenie nowym użytkownikom, aby przestali używać,return
prawdopodobnie nie pomoże, ale wprowadzenie ich do funkcjonalnego stylu przyniesie korzyści.@Paul
return
jest konieczny w trybie rozkazującym, ponieważ często chcesz wyjść z funkcji w różnych punktach pętli. Funkcjonalny styl nie używa pętli, a zatem nie potrzebujereturn
. W czysto funkcjonalnym stylu ostateczne wywołanie jest prawie zawsze pożądaną wartością zwrotną.W Pythonie funkcje wymagają
return
instrukcji. Jeśli jednak zaprogramowałeś swoją funkcję w stylu funkcjonalnym, prawdopodobnie będziesz mieć tylko jednąreturn
instrukcję: na końcu swojej funkcji.Korzystając z przykładu z innego postu StackOverflow, powiedzmy, że potrzebujemy funkcji, która zwróci,
TRUE
jeśli wszystkie wartości w danym odcinku będąx
nieparzyste. Możemy użyć dwóch stylów:W stylu funkcjonalnym zwracana wartość naturalnie spada na końcu funkcji. Ponownie wygląda bardziej jak funkcja matematyczna.
@GSee Przedstawione ostrzeżenia
?ifelse
są zdecydowanie interesujące, ale nie sądzę, aby próbowały zniechęcić do korzystania z tej funkcji. W rzeczywistościifelse
ma tę zaletę, że automatycznie wektoryzuje funkcje. Na przykład rozważ nieco zmodyfikowaną wersjęfoo
:Ta funkcja działa dobrze, gdy
length(a)
jest 1, ale jeśli przepisałeś ponowniefoo
za pomocąifelse
Teraz
foo
działa na dowolnej długościa
. W rzeczywistości zadziałałoby nawet, gdya
jest matrycą. Zwracanie wartości tego samego kształtu cotest
funkcja ułatwiająca wektoryzację, a nie problem.źródło
return
nie pasuje do funkcjonalnego stylu programowania. Niezależnie od tego, czy programuje się imperatywnie lub funkcjonalnie, na pewnym etapie funkcja lub podprogram musi coś zwrócić. Na przykład programowanie funkcjonalne w Pythonie nadal wymagareturn
instrukcji. Czy mógłbyś rozwinąć więcej na ten temat.ifelse(a,a,b)
to mój wkurzony zwierzak. Wygląda na to, że każda linia?ifelse
krzyczy: „nie używaj mnie zamiastif (a) {a} else b
”. np. „... zwraca wartość o takim samym kształcie jaktest
”, „jeśliyes
lubno
są za krótkie, ich elementy są poddawane recyklingowi.”, „tryb wyniku może zależeć od wartościtest
”, „atrybut klasy wyniku jest pobierane ztest
i może być nieodpowiednie dla wartości wybranych zyes
ino
”foo
nie ma większego sensu; zawsze zwróci PRAWDA lubb
. Użycieifelse
go zwróci 1 lub kilka PRAWDA i / lub 1 lub kilkab
s. Początkowo myślałem, że intencją tej funkcji było powiedzenie „jeśli jakaś instrukcja jest PRAWDA, zwróć coś, w przeciwnym razie zwróć coś innego”. Nie sądzę, że powinno to być wektoryzowane, ponieważ wtedy stałoby się „zwróci elementy jakiegoś obiektu, który jest PRAWDA, a dla wszystkich elementów, które nie sąb
Wydaje się, że bez
return()
tego jest szybszy ...____ EDYCJA__ _ __ _ __ _ __ _ __ _ ___
Przechodzę do innych testów porównawczych (
benchmark(fuu(x),foo(x),replications=1e7)
) i wynik jest odwrócony ... Spróbuję na serwerze.źródło
return()
, jedno jeśli nie. Jest całkowicie redundantny na końcu funkcji, ponieważfunction()
zwraca jego ostatnią wartość. Zauważysz to tylko w wielu powtórzeniach funkcji, w których niewiele robi się wewnętrznie, tak że kosztreturn()
staje się dużą częścią całkowitego czasu obliczeniowego funkcji.Problem z tym, że nie umieszcza się wyraźnie „return” na końcu, polega na tym, że jeśli dodaje się dodatkowe instrukcje na końcu metody, nagle zwracana wartość jest błędna:
Zwraca wartość
dosomething()
.Teraz przychodzimy następnego dnia i dodajemy nową linię:
Chcieliśmy, aby nasz kod zwrócił wartość
dosomething()
, ale zamiast tego już tego nie robi.Dzięki wyraźnemu zwrotowi staje się to naprawdę oczywiste:
Widzimy, że w tym kodzie jest coś dziwnego i napraw go:
źródło
return
nie jest skrótem, to właściwy styl programowania funkcjonalnego. Używanie niepotrzebnychreturn
wywołań funkcji jest przykładem programowania kultu .return
wyciągu i niezauważenie, że nie zostanie wykonane.dosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
Jest szybszy, ponieważ
return
w R jest funkcją (pierwotną), co oznacza, że użycie jej w kodzie pociąga za sobą koszt wywołania funkcji. Porównaj to z większością innych języków programowania, w którychreturn
jest słowem kluczowym, ale nie wywołaniem funkcji: nie przekłada się to na wykonanie kodu wykonawczego.To powiedziawszy, wywoływanie funkcji prymitywnej w ten sposób jest dość szybkie w R, a wywoływanie
return
powoduje niewielki narzut. To nie jest argument za pominięciemreturn
.Ponieważ nie ma powodu, aby z niego korzystać.
Ponieważ jest redundantny i nie dodaje użytecznej redundancji.
Żeby było jasne: nadmiarowość może czasem być przydatna . Ale większość redundancji nie jest tego rodzaju. Zamiast tego jest to rodzaj wizualnego bałaganu bez dodawania informacji: jest to programowy odpowiednik słowa wypełniającego lub chartjunk ).
Rozważ następujący przykład komentarza wyjaśniającego, który jest powszechnie uznawany za złą redundancję, ponieważ komentarz ten jedynie parafrazuje to, co już wyraża kod:
Używanie
return
w R należy do tej samej kategorii, ponieważ R jest funkcjonalnym językiem programowania , aw R każde wywołanie funkcji ma wartość . Jest to podstawowa właściwość R. A gdy zobaczysz kod R z perspektywy, że każde wyrażenie (w tym każde wywołanie funkcji) ma wartość, powstaje pytanie: „dlaczego powinienem używaćreturn
?” Musi być pozytywny powód, ponieważ domyślnie nie używa się go.Jednym z takich pozytywnych powodów jest zasygnalizowanie wczesnego wyjścia z funkcji, powiedzmy w klauzuli ochronnej :
Jest to prawidłowe, niepotrzebne użycie
return
. Jednak takie klauzule ochronne są rzadkie w języku R w porównaniu do innych języków, a ponieważ każde wyrażenie ma wartość, regularneif
nie wymagareturn
:Możemy nawet przepisać w
f
następujący sposób:… Gdzie
if (cond) expr
jest to samo coif (cond) expr else NULL
.Na koniec chciałbym uprzedzić trzy powszechne zastrzeżenia:
Niektórzy twierdzą, że użycie
return
dodaje przejrzystości, ponieważ sygnalizuje „ta funkcja zwraca wartość”. Ale jak wyjaśniono powyżej, każda funkcja zwraca coś w R. Myślenie oreturn
markerze zwracania wartości nie jest po prostu zbędne, ale aktywnie wprowadza w błąd .W związku z tym Zen of Python ma wspaniałą wskazówkę, której należy zawsze przestrzegać:
W jaki sposób upuszczenie nadmiarowe
return
nie narusza tego? Ponieważ wartość zwracana przez funkcję w języku funkcjonalnym jest zawsze jawna: jest to jej ostatnie wyrażenie. To znowu ten sam argument na temat jawności a redundancji.W rzeczywistości, jeśli chcesz jawności, użyj jej, aby podświetlić wyjątek od reguły: zaznacz funkcje, które nie zwracają znaczącej wartości, które są wywoływane tylko ze względu na ich skutki uboczne (takie jak
cat
). Z wyjątkiem R ma lepszy niż markerreturn
dla tego przypadku:invisible
. Na przykład pisałbymAle co z długimi funkcjami? Czy nie będzie łatwo zgubić, co jest zwracane?
Dwie odpowiedzi: po pierwsze, niezupełnie. Zasada jest jasna: ostatnim wyrażeniem funkcji jest jej wartość. Nie ma nic do śledzenia.
Ale co ważniejsze, problemem w długich funkcjach nie jest brak wyraźnych
return
znaczników. To długość funkcji . Długie funkcje prawie (?) Zawsze naruszają zasadę pojedynczej odpowiedzialności, a nawet jeśli nie, skorzystają na rozbiciu na części w celu zwiększenia czytelności.źródło
return
aby uczynić go bardziej podobnym do innych języków. Ale to zły argument: inne funkcjonalne języki programowania również nie używająreturn
. Używają go tylko imperatywne języki, w których nie każde wyrażenie ma wartość.return
wsparcia jest wyraźnie lepsze, i przeczytałem twoją odpowiedź z pełną krytyką. Twoja odpowiedź skłoniła mnie do refleksji na temat tego poglądu. Myślę, że potrzeba użyciareturn
jawnie (przynajmniej w moim przypadku) wiąże się z potrzebą lepszej możliwości zmiany moich funkcji w późniejszym czasie. Biorąc pod uwagę, że moje funkcje mogą być po prostu zbyt skomplikowane, teraz widzę, że celem ulepszenia mojego stylu programowania byłoby dążenie do strukturyzacji kodów w celu zachowania przejrzystości bezreturn
. Dziękuję za te refleksje i wgląd !!Uważam to
return
za podstęp. Zasadniczo wartość ostatniego wyrażenia obliczonego w funkcji staje się wartością funkcji - i ten ogólny wzorzec występuje w wielu miejscach. Wszystkie następujące oceny do 3:To,
return
co tak naprawdę nie oznacza, zwraca wartość (dzieje się to z nią lub bez niej), ale „przerywa” funkcję w nieregularny sposób. W tym sensie jest to najbliższy odpowiednik instrukcji GOTO w R (istnieją również break i next). Używamreturn
bardzo rzadko i nigdy na końcu funkcji.... można to przepisać, ponieważ
if(a) a else b
jest znacznie lepiej czytelne i mniej nawiasowe.return
Tutaj wcale nie ma takiej potrzeby . Mój prototypowy przypadek użycia „return” byłby jak ...Zasadniczo potrzeba wielu zwrotów sugeruje, że problem jest albo brzydki, albo źle zorganizowany. G
<>
return
tak naprawdę nie potrzebuje funkcji do działania: możesz jej użyć, aby wyrwać się z zestawu wyrażeń do oceny.źródło
return
(mój brzydki powyższy przykład jest bardzo sztuczny): załóżmy, że musisz sprawdzić, czy wartość jestNULL
lubNA
: w takich przypadkach zwróć pusty ciąg, w przeciwnym razie zwróćcharacter
wartość. Ale testis.na(NULL)
daje błąd, więc wygląda na to, że można to zrobić tylko z,if(is.null(x)) return("")
a następnie kontynuowaćif(is.na(x)) .....
. (Można użyćlength(x)==0
zamiast,is.null(x)
ale nadal nie można użyć,length(x)==0 | is.na(x)
jeślix
jestNULL
.)|
(wektoryzowane OR, gdzie oceniane są obie strony) zamiast||
(zwarcie OR, nie wektoryzowane, gdzie predykaty są kolejno oceniane). Rozważmyif (TRUE | stop()) print(1)
versusif (TRUE || stop()) print(1)
return
może zwiększyć czytelność kodu:źródło
foo <- function() a || b
(która jest IMO bardziej czytelna; w każdym razie nie ma „czystej” czytelności, ale czytelność w czyimś przekonaniu: są ludzie, którzy twierdzą, że język asemblera jest doskonale czytelny)Często pojawia się tutaj argument dotyczący redundancji. Moim zdaniem nie jest to wystarczający powód do pominięcia
return()
. Redundancja nie jest automatycznie złą rzeczą. Przy strategicznym użyciu nadmiarowość sprawia, że kod jest bardziej przejrzysty i łatwiejszy w utrzymaniu.Rozważ ten przykład: parametry funkcji często mają wartości domyślne. Dlatego określenie wartości, która jest taka sama jak wartość domyślna, jest zbędne. Tyle że to czyni zachowanie, którego się spodziewam. Nie trzeba wyciągać strony podręcznika funkcji, aby przypomnieć sobie, jakie są ustawienia domyślne. I nie martw się, że przyszła wersja funkcji zmieni jej ustawienia domyślne.
Z znikomą karą za wykonanie połączenia
return()
(zgodnie z testami zamieszczonymi tutaj przez innych) sprowadza się raczej do stylu niż do dobra i zła. Aby coś było „nie tak”, musi istnieć wyraźna wada i nikt tutaj nie wykazał w zadowalający sposób, że włączenie lub pominięciereturn()
ma stałą wadę. Wydaje się bardzo specyficzne dla danego przypadku i dla konkretnego użytkownika.Oto, w czym jestem.
Nie podobają mi się zmienne „osierocone”, jak w powyższym przykładzie. Czy
abcd
będzie częścią oświadczenia, którego nie skończyłem pisać? Czy jest to pozostałość splajnu / edycji w moim kodzie i należy go usunąć? Czy przypadkowo wkleiłem / przeniosłem coś z innego miejsca?Natomiast ten drugi przykład pokazuje mi, że jest to zamierzona zwracana wartość, a nie jakiś wypadek lub niekompletny kod. Dla mnie ta redundancja absolutnie nie jest bezużyteczna.
Oczywiście, kiedy funkcja zostanie zakończona i działa, mógłbym usunąć zwrot. Ale usunięcie go jest samo w sobie zbędnym dodatkowym krokiem i moim zdaniem bardziej bezużyteczne niż włączenie go
return()
w pierwszej kolejności.To powiedziawszy, nie używam
return()
w skrócie nienazwanych funkcji jednowierszowych. Tam stanowi dużą część kodu funkcji i dlatego powoduje głównie bałagan wizualny, który sprawia, że kod jest mniej czytelny. Ale w przypadku większych formalnie zdefiniowanych i nazwanych funkcji używam go i prawdopodobnie nadal tak będzie.źródło
return()
in R nic nie kosztuje. Jest obiektywnie zbędny, ale bycie „bezużytecznym” jest twoim subiektywnym osądem. Zbędne i bezużyteczne niekoniecznie są synonimami. W tym miejscu się nie zgadzamy.return
, i chociaż nie jestem przekonany, myślę, że jest potencjalnie ważny (zdecydowanie jest to język imperatywny… wierzę, że nie tłumaczy się na języki funkcjonalne).