Czytałem ostatnio wiele rzeczy na temat programowania funkcjonalnego i rozumiem większość z nich, ale jedyną rzeczą, której po prostu nie mogę zawinąć, jest kodowanie bezstanowe. Wydaje mi się, że uproszczenie programowania poprzez usunięcie stanu zmiennego jest jak „uproszczenie” samochodu poprzez usunięcie deski rozdzielczej: gotowy produkt może być prostszy, ale powodzenia w interakcji z użytkownikami końcowymi.
Prawie każda aplikacja użytkownika, o której myślę, zawiera stan jako podstawową koncepcję. Jeśli napiszesz dokument (lub wpis SO), stan zmienia się z każdym nowym wejściem. Lub jeśli grasz w grę wideo, istnieje mnóstwo zmiennych stanu, zaczynając od pozycji wszystkich postaci, które mają tendencję do ciągłego poruszania się. Jak możesz zrobić coś przydatnego bez śledzenia zmian wartości?
Za każdym razem, gdy znajduję coś, co omawia ten problem, jest napisany bardzo technicznie funkcjonalnie, który zakłada ciężkie tło FP, którego nie mam. Czy ktoś zna sposób na wyjaśnienie tego komuś, kto dobrze i dobrze rozumie kodowanie imperatywne, ale kto jest kompletnym n00b po stronie funkcjonalnej?
EDYCJA: Jak dotąd wiele odpowiedzi próbuje mnie przekonać o zaletach niezmiennych wartości. Rozumiem tę część. To ma sens. Nie rozumiem, w jaki sposób możesz śledzić wartości, które muszą się zmieniać i ciągle zmieniać, bez zmiennych zmiennych.
źródło
Odpowiedzi:
Jeśli jesteś zainteresowany, oto seria artykułów opisujących programowanie gier za pomocą Erlanga.
Prawdopodobnie nie spodoba ci się ta odpowiedź, ale nie dostaniesz funkcjonalnego programu, dopóki go nie użyjesz. Mogę wysłać próbki kodu i powiedzieć „Tutaj, nie widzisz ” - ale jeśli nie rozumiesz składni i zasad leżących u podstaw, twoje oczy po prostu się gapią. Z twojego punktu widzenia wygląda na to, że robię to samo co język imperatywny, ale po prostu ustanawiam wszelkie granice, aby celowo utrudnić programowanie. Z mojego punktu widzenia po prostu doświadczasz paradoksu Blub .
Na początku byłem sceptyczny, ale kilka lat temu wskoczyłem na funkcjonalny pociąg programistyczny i zakochałem się w nim. Sztuką programowania funkcjonalnego jest umiejętność rozpoznawania wzorców, określonych przypisań zmiennych i przenoszenia stanu imperatywnego na stos. Na przykład pętla for staje się rekurencją:
Nie jest bardzo ładna, ale mamy ten sam efekt bez mutacji. Oczywiście, gdy tylko jest to możliwe, lubimy całkowicie zapętlać i po prostu je wyodrębnić:
Metoda Seq.iter wyliczy kolekcję i wywoła anonimową funkcję dla każdego elementu. Bardzo przydatny :)
Wiem, że drukowanie liczb nie jest imponujące. Możemy jednak zastosować to samo podejście do gier: przytrzymaj cały stan na stosie i utwórz nowy obiekt z naszymi zmianami w wywołaniu rekurencyjnym. W ten sposób każda klatka jest bezstanową migawką gry, przy czym każda klatka po prostu tworzy zupełnie nowy obiekt z pożądanymi zmianami dowolnych bezstanowych obiektów wymagających aktualizacji. Pseudokod tego może być:
Wersje imperatywna i funkcjonalna są identyczne, ale wersja funkcjonalna wyraźnie nie wykorzystuje stanu zmiennego. Kod funkcjonalny utrzymuje cały stan na stosie - miłą rzeczą w tym podejściu jest to, że jeśli coś pójdzie nie tak, debugowanie jest łatwe, wszystko czego potrzebujesz to ślad stosu.
Skaluje się do dowolnej liczby obiektów w grze, ponieważ wszystkie obiekty (lub kolekcje powiązanych obiektów) mogą być renderowane we własnym wątku.
W językach funkcjonalnych zamiast mutować stan obiektów, po prostu zwracamy nowy obiekt z pożądanymi zmianami. Jest bardziej wydajny niż się wydaje. Na przykład struktury danych są bardzo łatwe do przedstawienia jako niezmienne struktury danych. Na przykład stosy są niezwykle łatwe do wdrożenia:
Powyższy kod konstruuje dwie niezmienne listy, dołącza je razem, aby utworzyć nową listę, i dołącza wyniki. Nigdzie w aplikacji nie jest używany żaden stan zmienny. Wygląda trochę nieporęcznie, ale to tylko dlatego, że C # jest pełnym językiem. Oto równoważny program w F #:
Nie wymaga modyfikacji w celu tworzenia i manipulowania listami. Prawie wszystkie struktury danych można łatwo przekonwertować na ich funkcjonalne odpowiedniki. Napisałem tutaj stronę , która zapewnia niezmienne implementacje stosów, kolejek, stosów lewaków, czerwono-czarnych drzew, leniwych list. Żaden fragment kodu nie zawiera żadnego stanu podlegającego zmianom. Aby „mutować” drzewo, tworzę zupełnie nowy z nowym węzłem, który chcę - jest to bardzo wydajne, ponieważ nie muszę robić kopii każdego węzła w drzewie, mogę ponownie użyć starych w moim nowym drzewo.
Korzystając z bardziej znaczącego przykładu, napisałem również ten parser SQL, który jest całkowicie bezstanowy (a przynajmniej mój kod jest bezstanowy, nie wiem, czy podstawowa biblioteka leksykalna jest bezstanowa).
Programowanie bezstanowe jest tak samo wyraziste i potężne, jak programowanie stanowe, wymaga jedynie odrobiny praktyki, aby nauczyć się, jak zacząć myśleć bezpaństwowo. Oczywiście „programowanie bezstanowe, gdy to możliwe, programowanie stanowe tam, gdzie to konieczne” wydaje się być mottem najbardziej nieczystych języków funkcjonalnych. Nie ma nic złego w spadaniu na rzeczy zmienne, gdy funkcjonalne podejście po prostu nie jest tak czyste ani wydajne.
źródło
Krótka odpowiedź: nie możesz.
Więc co to za zamieszanie związane z niezmiennością?
Jeśli dobrze znasz język imperatywny, to wiesz, że „globały są złe”. Czemu? Ponieważ wprowadzają (lub mogą potencjalnie wprowadzać) pewne bardzo trudne do rozplątania zależności w kodzie. I zależności nie są dobre; chcesz, aby Twój kod był modułowy . Części programu nie wpływają na inne części w jak najmniejszym stopniu. I FP doprowadza cię do świętego Graala modułowość: bez skutków ubocznych w ogóle . Po prostu masz swoje f (x) = y. Wstaw x, wyjmij. Brak zmian w x lub cokolwiek innego. FP sprawia, że przestajesz myśleć o stanie i zaczynasz myśleć w kategoriach wartości. Wszystkie twoje funkcje po prostu otrzymują wartości i tworzą nowe wartości.
Ma to kilka zalet.
Po pierwsze, brak efektów ubocznych oznacza prostsze programy, łatwiejsze do uzasadnienia. Nie martw się, że wprowadzenie nowej części programu zakłóci i spowoduje awarię istniejącej, działającej części.
Po drugie, sprawia, że program można w prosty sposób zrównoleglać (sprawna równoległość to inna sprawa).
Po trzecie, istnieją pewne możliwe zalety wydajności. Powiedz, że masz funkcję:
Teraz wpiszesz wartość 3 w, a otrzymasz wartość 6 w. Każdego razu. Ale możesz to zrobić również w trybie rozkazującym, prawda? Tak. Problem polega jednak na tym, że w trybie rozkazującym możesz zrobić jeszcze więcej . Mogę zrobić:
ale mógłbym też
Kompilator imperatywny nie wie, czy będę miał skutki uboczne, czy nie, co utrudnia optymalizację (tzn. Podwójne 2 nie muszą być za każdym razem 4). Funkcjonalny wie, że tego nie zrobię - dlatego może optymalizować za każdym razem, gdy zobaczy „podwójne 2”.
Teraz, mimo że tworzenie nowych wartości za każdym razem wydaje się niezwykle marnotrawstwem dla złożonych typów wartości w zakresie pamięci komputera, nie musi tak być. Ponieważ jeśli masz f (x) = y, a wartości xiy są „w większości takie same” (np. Drzewa, które różnią się tylko kilkoma liśćmi), to xiy mogą współdzielić części pamięci - ponieważ żadna z nich nie ulegnie mutacji .
Więc jeśli ta niezmienna rzecz jest tak wspaniała, dlaczego odpowiedziałem, że nie można zrobić nic użytecznego bez stanu zmiennego. Bez mutacji cały program byłby gigantyczną funkcją f (x) = y. To samo dotyczy wszystkich części twojego programu: po prostu funkcje i funkcje w „czystym” sensie. Jak powiedziałem, oznacza to f (x) = y za każdym razem. Na przykład readFile („myFile.txt”) musiałby za każdym razem zwracać tę samą wartość ciągu. Niezbyt przydatne.
Dlatego każdy FP zapewnia pewne środki do mutowania stanu. „Czyste” języki funkcjonalne (np. Haskell) robią to przy użyciu nieco przerażających pojęć, takich jak monady, podczas gdy „nieczyste” (np. ML) pozwalają na to bezpośrednio.
I oczywiście, języki funkcjonalne są dostarczane z wieloma innymi dodatkami, które zwiększają wydajność programowania, takimi jak funkcje pierwszej klasy itp.
źródło
int double(x){ return x * (++y); }
że bieżąca nadal będzie mieć 4, chociaż nadal++y
będzieZauważ, że stwierdzenie, że programowanie funkcjonalne nie ma „stanu”, jest nieco mylące i może być przyczyną zamieszania. Zdecydowanie nie ma „stanu zmiennego”, ale nadal może mieć manipulowane wartości; po prostu nie można ich zmienić na miejscu (np. musisz utworzyć nowe wartości ze starych wartości).
Jest to rażące nadmierne uproszczenie, ale wyobraź sobie, że masz język OO, w którym wszystkie właściwości klas są ustawione tylko raz w konstruktorze, wszystkie metody są funkcjami statycznymi. Nadal możesz wykonywać prawie dowolne obliczenia, stosując metody pobierające obiekty zawierające wszystkie wartości, których potrzebują do swoich obliczeń, a następnie zwracające nowe obiekty z wynikiem (być może nawet nową instancję tego samego obiektu).
Przetłumaczenie istniejącego kodu na ten paradygmat może być „trudne”, ale dzieje się tak, ponieważ naprawdę wymaga zupełnie innego sposobu myślenia o kodzie. Jako efekt uboczny w większości przypadków masz dużo okazji do równoległości za darmo.
Dodatek: (Jeśli chodzi o edycję sposobu śledzenia wartości, które należy zmienić)
Będą one oczywiście przechowywane w niezmiennej strukturze danych ...
Nie jest to sugerowane „rozwiązanie”, ale najłatwiejszym sposobem, aby przekonać się, że to zawsze zadziała, jest zapisanie tych niezmiennych wartości w strukturze podobnej do mapy (słownika / tablicy mieszającej), kluczowanej „nazwą zmiennej”.
Oczywiście w praktycznych rozwiązaniach zastosowałbyś bardziej rozsądne podejście, ale pokazuje to ten najgorszy przypadek, jeśli nic innego nie zadziałałoby, możesz „zasymulować” stan zmienny za pomocą mapy, którą przenosisz przez drzewo wywołań.
źródło
Myślę, że istnieje niewielkie nieporozumienie. Czyste programy funkcjonalne mają stan. Różnica polega na sposobie modelowania tego stanu. W czystym programowaniu funkcjonalnym stanem manipulują funkcje, które przyjmują pewien stan i zwracają następny stan. Sekwencjonowanie przez stany jest następnie osiągane przez przekazanie stanu przez sekwencję czystych funkcji.
W ten sposób można modelować nawet globalny stan zmiennych. Na przykład w Haskell program jest funkcją od świata do świata. To znaczy, przechodzisz przez cały wszechświat , a program zwraca nowy wszechświat. W praktyce jednak musisz jedynie wchodzić w te części wszechświata, którymi naprawdę interesuje się twój program. Programy zwracają sekwencję działań, które służą jako instrukcje dla środowiska operacyjnego, w którym działa program.
Chciałeś zobaczyć to wyjaśnione w kategoriach programowania imperatywnego. OK, spójrzmy na naprawdę proste programowanie imperatywne w funkcjonalnym języku.
Rozważ ten kod:
Dość standardowy kod imperatywny. Nie robi nic interesującego, ale można to zilustrować ilustracją. Myślę, że zgodzisz się, że w grę wchodzi tu państwo. Wartość zmiennej x zmienia się w czasie. Teraz zmieńmy nieco notację poprzez wynalezienie nowej składni:
Umieść nawiasy, aby wyjaśnić, co to oznacza:
Widzicie więc, stan jest modelowany przez sekwencję czystych wyrażeń, które wiążą wolne zmienne następujących wyrażeń.
Przekonasz się, że ten wzór może modelować dowolny stan, nawet IO.
źródło
Oto, w jaki sposób piszesz kod bez stanu zmiennego : zamiast zmieniać stan w zmienne zmienne, umieszczasz go w parametrach funkcji. Zamiast pisać pętle, piszesz funkcje rekurencyjne. Na przykład ten kod imperatywny:
staje się tym kodem funkcjonalnym (składnia podobna do schematu):
lub ten kod Haskellisha
Co do tego, dlaczego programiści funkcjonalni lubią to robić (o co nie pytałeś), im więcej kawałków twojego programu jest bezstanowych, tym więcej jest sposobów na łączenie kawałków bez niczego . Siła bezpaństwowego paradygmatu nie polega na bezpaństwowości (lub czystości) per se , ale na zdolności, jaką daje ci pisanie potężnych funkcji wielokrotnego użytku i łączenie ich.
Dobry samouczek z wieloma przykładami można znaleźć w artykule Johna Hughesa Dlaczego programowanie funkcjonalne ma znaczenie .
źródło
To tylko różne sposoby robienia tego samego.
Rozważ prosty przykład, taki jak dodanie liczb 3, 5 i 10. Wyobraź sobie, że możesz to zrobić, najpierw zmieniając wartość 3, dodając 5, a następnie dodając 10 do tego „3”, a następnie wyprowadzając bieżącą wartość „ 3 "(18). Wydaje się to absurdalnie śmieszne, ale w rzeczywistości jest to sposób, w jaki często odbywa się programowanie imperatywne oparte na stanie. Rzeczywiście, możesz mieć wiele różnych „3”, które mają wartość 3, ale są różne. Wszystko to wydaje się dziwne, ponieważ jesteśmy tak głęboko zakorzenieni w, bardzo rozsądnie, przekonaniu, że liczby są niezmienne.
Pomyśl teraz o dodaniu 3, 5 i 10, gdy przyjmujesz wartości za niezmienne. Dodajesz 3 i 5, aby uzyskać kolejną wartość, 8, a następnie dodajesz 10 do tej wartości, aby uzyskać jeszcze jedną wartość, 18.
Są to równoważne sposoby robienia tego samego. Wszystkie niezbędne informacje istnieją w obu metodach, ale w różnych formach. W jednym informacja istnieje jako stan oraz w zasadach zmiany stanu. Z drugiej strony informacje istnieją w niezmiennych danych i definicjach funkcjonalnych.
źródło
Spóźniłem się do dyskusji, ale chciałem dodać kilka punktów dla osób, które mają problemy z funkcjonalnym programowaniem.
Najpierw sposób rozkazujący (w pseudokodzie)
Teraz funkcjonalny sposób (w pseudokodzie). Opieram się mocno na operatorze trójskładnikowym, ponieważ chcę, aby ludzie z niezbędnego pochodzenia mogli faktycznie czytać ten kod. Jeśli więc nie używasz operatora trójskładnikowego (zawsze unikałem go w moich bezwzględnych dniach), oto jak to działa.
Możesz połączyć wyrażenie trójskładnikowe, umieszczając nowe wyrażenie trójskładnikowe zamiast wyrażenia fałszywego
Mając to na uwadze, oto wersja funkcjonalna.
To jest trywialny przykład. Gdyby to poruszało ludzi w świecie gry, musiałbyś wprowadzić efekty uboczne, takie jak narysowanie aktualnej pozycji obiektu na ekranie i wprowadzenie odrobiny opóźnienia w każdym wywołaniu w zależności od tego, jak szybko obiekt się porusza. Ale nadal nie potrzebujesz stanu zmiennego.
Lekcja jest taka, że języki funkcjonalne „mutują”, wywołując funkcję z różnymi parametrami. Oczywiście to tak naprawdę nie mutuje żadnych zmiennych, ale w ten sposób uzyskuje się podobny efekt. Oznacza to, że musisz przyzwyczaić się do myślenia rekurencyjnego, jeśli chcesz programować funkcjonalnie.
Nauka rekurencyjnego myślenia nie jest trudna, ale wymaga zarówno praktyki, jak i zestawu narzędzi. Ta niewielka część książki „Naucz się języka Java”, w której użyli rekurencji do obliczenia silni, nie ją wycina. Potrzebujesz zestawu umiejętności, takich jak tworzenie iteracyjnych procesów z rekurencji (dlatego rekurencja ogona jest niezbędna dla funkcjonalnego języka), kontynuacji, niezmienników itp. Nie zrobiłbyś programowania OO bez wiedzy o modyfikatorach dostępu, interfejsach itp. To samo do programowania funkcjonalnego.
Moje zalecenie to zrobić Mały Schemer (zauważ, że mówię „rób”, a nie „czytaj”), a następnie wykonaj wszystkie ćwiczenia w SICP. Kiedy skończysz, będziesz miał inny mózg niż na początku.
źródło
W rzeczywistości dość łatwo jest mieć coś, co wygląda jak stan zmienny, nawet w językach bez tego stanu.
Rozważ funkcję z typem
s -> (a, s)
. Tłumaczenie ze składni Haskella oznacza funkcję, która pobiera jeden parametr typu „s
” i zwraca parę wartości typów „a
” i „s
”. Jeślis
jest to typ naszego stanu, ta funkcja przyjmuje jeden stan i zwraca nowy stan, a być może także wartość (zawsze możesz zwrócić „unit” aka()
, co jest swego rodzaju odpowiednikiem „void
” w C / C ++, jako „a
” rodzaj). Jeśli połączysz kilka wywołań funkcji tego typu (przywracanie stanu z jednej funkcji i przekazywanie jej do następnej), masz stan „zmienny” (w rzeczywistości jesteś w każdej funkcji, tworząc nowy stan i porzucając stary) ).Łatwiej jest to zrozumieć, jeśli wyobrażasz sobie zmienny stan jako „przestrzeń”, w której wykonuje się twój program, a następnie pomyśl o wymiarze czasu. W chwili t1 „przestrzeń” jest w pewnym stanie (powiedzmy na przykład, że pewne miejsce w pamięci ma wartość 5). W późniejszym momencie t2 jest w innym stanie (na przykład lokalizacja pamięci ma teraz wartość 10). Każdy z tych „wycinków” jest stanem i jest niezmienny (nie można cofnąć się w czasie, aby je zmienić). Z tego punktu widzenia przeszedłeś od pełnej czasoprzestrzeni ze strzałką czasową (stan zmienny) do zestawu wycinków czasoprzestrzeni (kilka niezmiennych stanów), a twój program traktuje każdy wycinek jako wartość i oblicza każdy z nich z nich jako funkcja zastosowana do poprzedniej.
OK, może nie było to łatwiejsze do zrozumienia :-)
Może się wydawać, że użyteczne jest jawne przedstawienie całego stanu programu jako wartości, którą należy utworzyć tylko po to, aby została odrzucona w następnej chwili (zaraz po utworzeniu nowej). W przypadku niektórych algorytmów może to być naturalne, ale gdy tak nie jest, istnieje inna sztuczka. Zamiast stanu rzeczywistego można użyć stanu fałszywego, który jest niczym więcej niż znacznikiem (nazwijmy typ tego fałszywego stanu
State#
). Ten fałszywy stan istnieje z punktu widzenia języka i jest przekazywany jak każda inna wartość, ale kompilator całkowicie go pomija podczas generowania kodu maszynowego. Służy jedynie do zaznaczenia sekwencji wykonania.Załóżmy na przykład, że kompilator udostępnia nam następujące funkcje:
Tłumaczenie z tych deklaracji podobnych do Haskella,
readRef
otrzymuje coś, co przypomina wskaźnik lub uchwyt do wartości typu „a
” i fałszywego stanu, i zwraca wartość typu „a
” wskazaną przez pierwszy parametr i nowy fałszywy stan.writeRef
jest podobny, ale zmienia zamiast tego wskazaną wartość.Jeśli wywołasz,
readRef
a następnie przekażesz fałszywy stan zwrócony przezwriteRef
(być może z innymi wywołaniami niezwiązanych funkcji w środku; te wartości stanu tworzą „łańcuch” wywołań funkcji), zwróci zapisaną wartość. Możesz zadzwonićwriteRef
ponownie z tym samym wskaźnikiem / uchwytem i zapisze w tej samej lokalizacji w pamięci - ale ponieważ koncepcyjnie zwraca nowy (fałszywy) stan, (fałszywy) stan jest nadal możliwy do przypisania (nowy został „utworzony” „). Kompilator będzie wywoływał funkcje w kolejności, w jakiej musiałby je wywoływać, gdyby istniała zmienna stanu rzeczywistego, którą trzeba było obliczyć, ale jedynym stanem, który istnieje, jest pełny (zmienny) stan rzeczywistego sprzętu.(Ci, którzy znają Haskell zauważy I uprościć wiele rzeczy, pominięte i kilka ważnych szczegółów. Dla tych, którzy chcą zobaczyć więcej szczegółów, zapoznać się
Control.Monad.State
zmtl
, i naST s
iIO
(akaST RealWorld
) monad).Możesz się zastanawiać, dlaczego robisz to w taki okrągły sposób (zamiast po prostu mieć zmienny stan w języku). Prawdziwą zaletą jest to, że poprawiłeś stan swojego programu. To, co wcześniej było niejawne (stan twojego programu był globalny, pozwalając na działania takie jak działanie na odległość ) jest teraz jawne. Funkcje, które nie odbierają i nie zwracają stanu, nie mogą go modyfikować ani na niego wpływać; są „czyste”. Co więcej, możesz mieć osobne wątki stanu, a przy odrobinie magii typu można je wykorzystać do osadzenia obliczeń imperatywnych w czystym, nie czyniąc go nieczystym (
ST
monada w Haskell jest zwykle używana do tej sztuczki;State#
wspomniałem powyżej jest fakt GHC użytkownikaState# s
, używane przez jego wdrażaniaST
iIO
monady).źródło
Programowanie funkcjonalne pozwala uniknąć stanu i podkreślafunkcjonalność. Nigdy nie ma czegoś takiego jak brak stanu, chociaż stan może być czymś niezmiennym lub upieczonym w architekturze tego, z czym pracujesz. Rozważ różnicę między statycznym serwerem WWW, który po prostu ładuje pliki z systemu plików, a programem, który implementuje kostkę Rubika. Pierwszy z nich zostanie zaimplementowany w zakresie funkcji zaprojektowanych do przekształcenia żądania w żądanie ścieżki do pliku w odpowiedź z zawartości tego pliku. Praktycznie żaden stan nie jest potrzebny poza drobną konfiguracją („stan” systemu plików jest tak naprawdę poza zakresem programu. Program działa w ten sam sposób, niezależnie od stanu plików). W tym ostatnim trzeba jednak modelować kostkę i implementację programu, w jaki sposób operacje na tej kostce zmieniają jej stan.
źródło
Oprócz wspaniałych odpowiedzi udzielanych przez innych, pomyśl o klasach
Integer
iString
Javie. Instancje tych klas są niezmienne, ale to nie czyni ich bezużytecznymi tylko dlatego, że ich instancji nie można zmienić. Niezmienność daje pewne bezpieczeństwo. Wiesz, że jeśli użyjesz instancji String lub Integer jako klucza do aMap
, klucza nie można zmienić. Porównaj to zDate
klasą w Javie:Po cichu zmieniłeś klucz na swojej mapie! Praca z niezmiennymi obiektami, takimi jak Programowanie Funkcjonalne, jest o wiele czystsza. Łatwiej jest zrozumieć, jakie skutki uboczne występują - żaden! Oznacza to, że jest to łatwiejsze dla programisty, a także dla optymalizatora.
źródło
W przypadku wysoce interaktywnych aplikacji, takich jak gry, Functional Reactive Programming jest twoim przyjacielem: jeśli możesz sformułować właściwości świata gry jako zmienne w czasie wartości (i / lub strumienie zdarzeń), jesteś gotowy! Te formuły będą czasem nawet bardziej naturalne i odsłaniające intencje niż mutowanie stanu, np. W przypadku poruszającej się kuli można bezpośrednio użyć znanego prawa x = v * t . Co więcej, tak napisane reguły gry komponują się lepiej niż abstrakcje obiektowe. Na przykład w tym przypadku prędkość piłki może być wartością zmienną w czasie, która zależy od strumienia zdarzeń składającego się z zderzeń piłki. Aby uzyskać więcej konkretnych rozważań dotyczących projektowania, zobacz Tworzenie gier w wiązach .
źródło
Przy użyciu kreatywności i dopasowania wzorców powstały bezpaństwowe gry:
oraz toczące się dema:
i wizualizacje:
źródło
W ten sposób FORTRAN działałby bez WSPÓLNYCH bloków: pisałbyś metody, które miały przekazane wartości i zmienne lokalne. Otóż to.
Programowanie obiektowe zbliżyło nas do stanu i zachowania, ale był to nowy pomysł, kiedy po raz pierwszy zetknąłem się z nim z C ++ w 1994 roku.
Rany, byłem inżynierem funkcjonalnym, kiedy byłem inżynierem mechanikiem i nie wiedziałem o tym!
źródło
Pamiętaj: języki funkcjonalne są kompletne w Turingu. Dlatego każde przydatne zadanie, które można wykonać w języku imperatywnym, można wykonać w języku funkcjonalnym. Pod koniec dnia myślę jednak, że można powiedzieć o podejściu hybrydowym. Języki takie jak F # i Clojure (i jestem pewien, że inne) zachęcają do bezpaństwowego projektowania, ale pozwalają na zmienność w razie potrzeby.
źródło
Nie możesz mieć czysto funkcjonalnego języka, który byłby użyteczny. Zawsze będzie poziom zmienności, z którym będziesz musiał sobie poradzić, IO jest jednym z przykładów.
Pomyśl o językach funkcjonalnych jako o innym używanym narzędziu. Jest dobry dla niektórych rzeczy, ale nie dla innych. Podany przykład gry może nie być najlepszym sposobem na użycie funkcjonalnego języka, przynajmniej ekran będzie miał zmienny stan, z którym nie można nic zrobić z FP. Sposób, w jaki myślisz o problemie i rodzaj problemów, które rozwiązujesz za pomocą FP, będzie inny niż te, do których przywykłeś w programowaniu imperatywnym.
źródło
Używając wielu rekurencji.
Kółko i krzyżyk w F # (język funkcjonalny.)
źródło
To jest bardzo proste. Możesz użyć tyle zmiennych, ile chcesz w programowaniu funkcjonalnym ... ale tylko wtedy, gdy są to zmienne lokalne (zawarte w funkcjach). Więc po prostu zawiń swój kod w funkcjach, przekaż wartości między tymi funkcjami (jako przekazane parametry i zwrócone wartości) ... i to wszystko!
Oto przykład:
źródło