Jakie są korzyści z przejrzystości odniesienia dla programisty?


18

Jakie są korzyści z przejrzystości referencyjnej w programowaniu ?

RT stanowi jedną z głównych różnic między paradygmatem funkcjonalnym a imperatywnym i jest często używany przez zwolenników paradygmatu funkcjonalnego jako wyraźną przewagę nad trybem imperatywnym; ale we wszystkich swoich wysiłkach ci zwolennicy nigdy nie wyjaśniają, dlaczego dla mnie jako programisty jest to korzyść .

Jasne, otrzymają wyjaśnienia akademickie na temat tego, jak „czysty” i „elegancki”, ale w jaki sposób sprawia, że ​​jest lepszy niż mniej „czysty” kod? Jakie korzyści przyniesie mi codzienne programowanie?

Uwaga: nie jest to duplikat Co to jest przejrzystość referencyjna? Te ostatnie adresy tematem , co jest RT, natomiast kwestia ta adressses swoje zalety (które nie mogą być tak intuicyjne).




3
Przezroczystość referencyjna pozwala na zastosowanie rozumowania równań do: 1) Udowodnienia właściwości kodu i 2) pisania programów. Istnieje kilka książek o Haskell, w których autorzy powinni zacząć od niektórych równań, które mają być wypełnione przez funkcję, a używając tylko rozumowania równania, uzyskujemy implementację wspomnianej funkcji, co jest z pewnością poprawne. To, ile można to zastosować w programowaniu „na co dzień”, prawdopodobnie zależy od kontekstu ...
Bakuriu,

2
@err Czy podoba ci się kod, który łatwiej jest refaktoryzować, ponieważ wiesz, czy dwukrotne wywołanie funkcji jest tym samym, co zapisanie jej wartości w zmiennej, a następnie dwukrotne użycie tej zmiennej? Czy powiedziałbyś, że to korzyść dla twojego codziennego programowania?
Andres F.,

Korzyścią jest to, że nie musisz tracić czasu na myślenie o referencyjnej nieprzejrzystości. Trochę jak korzyści płynące ze zmiennych, że nie musisz tracić czasu na myślenie o przydziale rejestrów.
user253751,

Odpowiedzi:


37

Zaletą jest to, że czyste funkcje ułatwiają zrozumienie kodu. Innymi słowy, skutki uboczne zwiększają złożoność kodu.

Weź przykład computeProductPricemetody.

Czysta metoda poprosiłaby cię o ilość produktu, walutę itp. Wiesz, że ilekroć metoda jest wywoływana z tymi samymi argumentami, zawsze da ten sam wynik.

  • Możesz go nawet buforować i używać wersji z pamięci podręcznej.
  • Możesz sprawić, by był leniwy i odłożył wywołanie, kiedy naprawdę go potrzebujesz, wiedząc, że wartość nie zmieni się w międzyczasie.
  • Możesz wywołać tę metodę wiele razy, wiedząc, że nie będzie ona miała skutków ubocznych.
  • Możesz rozumować samą metodę w izolacji od świata, wiedząc, że wszystko, czego potrzebuje, to argumenty.

Nieoczyszczona metoda będzie bardziej złożona w użyciu i debugowaniu. Ponieważ zależy to od stanu zmiennych innych niż argumenty i ewentualnie ich zmiany, oznacza to, że może wywoływać różne wyniki, gdy jest wywoływany wiele razy, lub nie może zachowywać się tak samo, gdy w ogóle nie jest wywoływany lub wywoływany zbyt wcześnie lub za późno.

Przykład

Wyobraź sobie, że w ramach jest metoda, która analizuje liczbę:

decimal math.parse(string t)

Nie ma przezroczystości referencyjnej, ponieważ zależy od:

  • Zmienna środowiskowa określająca system numeracji, czyli Base 10 lub coś innego.

  • Zmienna w mathbibliotece, która określa dokładność analizowanych liczb. Więc z wartością 1parsowania łańcucha "12.3456"da 12.3.

  • Kultura, która określa oczekiwane formatowanie. Na przykład, z fr-FR, parsowanie "12.345"da 12345, ponieważ znak separacji powinien być ,, nie.

Wyobraź sobie, jak łatwa lub trudna byłaby praca z taką metodą. Przy tym samym wejściu możesz uzyskać radykalnie różne wyniki w zależności od momentu wywołania metody, ponieważ coś gdzieś zmieniło zmienną środowiskową lub zmieniło kulturę lub ustawiało inną precyzję. Niedeterministyczny charakter metody prowadziłby do większej liczby błędów i koszmaru debugowania. Wywoływanie math.parse("12345")i uzyskiwanie 5349jako odpowiedź, ponieważ jakiś równoległy kod analizował liczby ósemkowe, nie jest przyjemny.

Jak naprawić tę oczywiście zepsutą metodę? Wprowadzając przejrzystość referencyjną. Innymi słowy, pozbywając się stanu globalnego i przenosząc wszystko do parametrów metody:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Teraz, gdy metoda jest czysta, wiesz, że bez względu na to, kiedy wywołasz metodę, zawsze będzie generować ten sam wynik dla tych samych argumentów.


4
Tylko dodatek: przejrzystość referencyjna dotyczy wszystkich wyrażeń w języku, a nie tylko funkcji.
ogrodnik

3
Pamiętaj, że istnieją ograniczenia dotyczące przejrzystości. Uczynienie packet = socket.recv()odniesienia przejrzystym raczej niweczy sens funkcji.
Mark

1
Powinno być kultura = kultury. Zmienne. Chyba że chcesz przypadkowo stworzyć oprogramowanie, które działa poprawnie tylko w Stanach Zjednoczonych.
user253751,

@immibis: hm, dobre pytanie. Jakie byłyby reguły analizy invariant? Albo są takie same jak dla en_us, w którym to przypadku, po co zawracać sobie głowę, lub odpowiadają innemu krajowi, w którym to przypadku, który to kraj i dlaczego zamiast tego en_us, lub mają swoje szczegółowe zasady, które i tak nie pasują do żadnego kraju , co byłoby bezużyteczne. Naprawdę nie ma „prawdziwej odpowiedzi” pomiędzy 12,345.67i 12 345,67: wszelkie „domyślne reguły” będą działać w kilku krajach i nie będą działać w przypadku innych.
Arseni Mourzenko

3
@ArseniMourzenko Zasadniczo jest to „najniższy wspólny mianownik” i podobny do składni używanej przez wiele języków programowania (która jest również niezmienna dla kultury). 12345analizuje jak 12345, 12 345lub 12,345czy 12.345jest to błąd. 12.345parsowane jako niezmienna liczba zmiennoprzecinkowa zawsze daje 12,345, zgodnie z konwencją użycia języka programowania. jako separator dziesiętny. Łańcuchy są sortowane według punktów kodu Unicode i rozróżniają małe i wielkie litery. I tak dalej.
user253751

11

Czy często dodajesz punkt przerwania do punktu w kodzie i uruchamiasz aplikację w debuggerze, aby dowiedzieć się, co się dzieje? Jeśli tak, to głównie dlatego, że nie używasz przezroczystości referencyjnej (RT) w swoich projektach. I tak trzeba uruchomić kod, aby dowiedzieć się, co on robi.

Chodzi o RT, że kod jest wysoce deterministyczny, tj. Możesz czytać kod i sprawdzać, co robi za każdym razem dla tego samego zestawu danych wejściowych. Gdy zaczniesz dodawać zmienne mutujące, z których niektóre mają zasięg wykraczający poza jedną funkcję, nie możesz po prostu odczytać kodu. Taki kod musi zostać wykonany w twojej głowie lub w debugerze, aby dowiedzieć się, jak to naprawdę działa.

Im prostszy kod jest do odczytania i uzasadnienia, tym łatwiej jest utrzymywać i wykrywać błędy, dzięki czemu oszczędza czas i pieniądze dla ciebie i twojego pracodawcy.


10
„Gdy zaczniesz dodawać mutujące zmienne, z których niektóre mają więcej niż jedną funkcję, nie możesz po prostu odczytać kodu, musisz go wykonać w głowie lub w debugerze, aby dowiedzieć się, jak to naprawdę działa. ": Słuszna uwaga. Innymi słowy, przejrzystość referencyjna oznacza nie tylko, że fragment kodu zawsze będzie dawał ten sam wynik dla tych samych danych wejściowych, ale także, że uzyskany wynik jest jedynym efektem tego fragmentu kodu, że nie ma innej, ukrytej strony efekt taki jak zmiana jakiejś zmiennej, która została zdefiniowana daleko w innym module.
Giorgio

To dobra uwaga. Mam trochę problemu z tym, że prostszy jest kod do odczytu / rozumowania, ponieważ prostszy do odczytania lub rozumowania jest nieco niejasnym i subiektywnym atrybutem kodu.
Eyal Roth,

Kiedy zaczniesz dodawać zmienne mutujące, z których niektóre mają zakres wykraczający poza jedną funkcję, ale dlaczego odkładanie jest odradzane, nawet jeśli zakres zmiennych ma charakter lokalny?
rahulaga_dev

9

Ludzie rzucają się na określenie „łatwiej zrozumieć”, ale nigdy nie wyjaśniają, co to znaczy. Rozważ następujący przykład:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

result1i result2takie same czy różne? Bez odniesienia referencyjnego nie masz pojęcia. Musisz się dokładnie zapoznać z treścią, fooaby się upewnić, i ewentualnie z treścią jakichkolwiek foowywołań funkcji i tak dalej.

Ludzie nie zauważają tego ciężaru, ponieważ są do niego przyzwyczajeni, ale jeśli pójdziesz do pracy w czysto funkcjonalnym środowisku przez miesiąc lub dwa, a potem wrócisz, poczujesz to i to jest wielka sprawa .

Jest tak wiele mechanizmów obronnych, które ludzie robią, aby obejść brak przejrzystości odniesienia. Na mój mały przykład mogę chcieć pozostać result1w pamięci, ponieważ nie wiedziałbym, czy to się zmieni. Następnie mam kod z dwoma stanami: przedtem result1był przechowywany i później. Dzięki przezroczystości referencyjnej mogę ją łatwo ponownie przeliczyć, o ile ponowne obliczenie nie zajmuje dużo czasu.


1
Wspomniałeś, że przejrzystość referencyjna pozwala ci rozumować wynik wywołań foo () i wiedzieć, czy result1i result2są takie same. Innym ważnym aspektem jest to, że jeśli foo("bar", 12)jest względnie przejrzysty, to nie musisz zadawać sobie pytania, czy to wywołanie wywołało jakieś efekty gdzie indziej (ustaw niektóre zmienne? Usuń plik? Cokolwiek).
Giorgio

Jedyną „integralnością referencyjną”, którą znam, są relacyjne bazy danych.
Mark

1
@ Mark To literówka. Karl miał na myśli przejrzystość referencyjną, co wynika z reszty jego odpowiedzi.
Andres F.,

6

Powiedziałbym: przejrzystość referencyjna jest nie tylko dobra dla programowania funkcjonalnego, ale dla każdego, kto pracuje z funkcjami, ponieważ jest to zasada najmniejszego zdziwienia.

Masz funkcję i możesz lepiej rozumować, co ona robi, ponieważ nie ma żadnych czynników zewnętrznych, które musisz wziąć pod uwagę, dla danego wejścia wynik zawsze będzie taki sam. Nawet w moim imperatywnym języku staram się w jak największym stopniu przestrzegać tego paradygmatu, następną rzeczą, która w zasadzie automatycznie z tego wynika: małe, łatwe do zrozumienia funkcje zamiast makabrycznych 1000+ funkcji liniowych, w których czasami uruchamiam.

Te duże funkcje pełnią magię i boję się ich dotykać, ponieważ mogą się popsuć w spektakularny sposób.

Czyste funkcje to nie tylko programowanie funkcjonalne, ale każdy program.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.