FP - wady i zalety

AG
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5
0

Języki programowania są mniej lub bardziej funkcyjne. Co ładnie obrazuje odpowiedź z SO: https://stackoverflow.com/a/13600858

Z tego co rozumiem, im bardziej język jest funkcyjny tym częściej stwarza okazję do tego by zapisać kod jako wyrażenie.

Zastanawiam się co dobrego z tego wynika? Czy funkcyjność pomaga, a kiedy przeszkadza?

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
1

Mam wrażenie że ludzie dopowiadają do FP, więcej, niż na prawdę w nim jest. Nie tylko w FP, do OOP i innych paradygmatów również. Sam osobiście definiuję paradygmat funkcyjny, tak że raz stworzony stan nie jest mutowany. Jeśli pisząc w Javie usuniesz wszelkie modyfikację stanu, według mnie piszesz funkcyjnie. Lambdy, wyrażenia, cała matematyka, to są dodatkowe usprawnienia które można stosować FP, ale można też je stosować poza FP.

Np. korzystanie z lambd, nie jest warunkiem wystarczającym żeby powiedzieć że jest FP (bo w lambdzie można mutować stan), ani koniecznym (bo da się pisać funkcyjnie bez lambd).

Zalety niemutowalności można mnożyć.

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1020
5

W każdym języku (poza assemblerem) można pisać mniej lub bardziej funkcyjnie. Np C ma taki super ficzer jak (1 + 2) * 3 co jest bardziej funkcyjne niż operacje na rejestrach. IMO zawsze warto pisać kod funkcyjny, zwłaszcza jeśli chodzi o interfejs wysokopoziomowy (sygnatura funkcji), bo to czy w środku funkcji użyjesz map czy for nie jest super ważne

Polecam artykuł http://www.sevangelatos.com/john-carmack-on/

Kiedy FP działa słabo? Jak w twojej aplikacji nie da się łatwo podzielić na czyste kawałki kodu, bo wszystko jest przeplatane efektami uboczymi . Dobrym przykładem jest system operacyjny, gdzie co chwile masz jakieś przerwania, globalny stan, locki i ręczne zarządzanie pamięcią. Inny problem to niska wydajność, bo w FP robi się dużo kopii

Escanor16
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 367
8

Mam wrażenie że co jakiś czas powstają te sam posty, i tak w kółko, wg moich wyliczeń za dwa-trzy tygodnie powinien pojawić się kolejny post o metodach statycznych

AG
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5
0
Riddle napisał(a):

Mam wrażenie że ludzie dopowiadają do FP, więcej, niż na prawdę w nim jest. Nie tylko w FP, do OOP i innych paradygmatów również. Sam osobiście definiuję paradygmat funkcyjny, tak że raz stworzony stan nie jest mutowany. Jeśli pisząc w Javie usuniesz wszelkie modyfikację stanu, według mnie piszesz funkcyjnie. Lambdy, wyrażenia, cała matematyka, to są dodatkowe usprawnienia które można stosować FP, ale można też je stosować poza FP.

Np. korzystanie z lambd, nie jest warunkiem wystarczającym żeby powiedzieć że jest FP (bo w lambdzie można mutować stan), ani koniecznym (bo da się pisać funkcyjnie bez lambd).

Zalety niemutowalności można mnożyć.

Ale co konkretnie postrzegasz za zaletę? Piszesz niemutowalność, ale wraz z tym nie ma żadnej informacji.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
again napisał(a):

Ale co konkretnie postrzegasz za zaletę? Piszesz niemutowalność, ale wraz z tym nie ma żadnej informacji.

Z takich głównych, dla mnie, to łatwiejszy refaktor oraz łatwiej rozumieć flow programu. Są dodatkowe, jak np. to że immutable jest automatycznie thread-safe.

KamilAdam
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Silesia/Marki
  • Postów: 5549
0
again napisał(a):

Z tego co rozumiem, im bardziej język jest funkcyjny tym częściej stwarza okazję do tego by zapisać kod jako wyrażenie.

Zastanawiam się co dobrego z tego wynika? Czy funkcyjność pomaga, a kiedy przeszkadza?

Umiesz Javę? porównaj starego swittcha z nowym, albo napisz kod który wymaga nowego switcha a potem przepisz go na starego.

Dla mnie problem jest taki iż jak mam funkcję/metodę która zwraca void to łatwo takie badziewie przeoczyć. Dlatego wyrażenia lepsze.

Patrząc na jezyki totalnie funkcyjne to w takim Haskellu wszystko jest wyrażeniem, ale dalej można coś przeoczyć jak mamy często IO (), bo to trochę taki haskellowy odpowiednik void, ale jednak wartość

AG
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5
0

Z tym flow to raczej pojedyncze funkcje niż cały program, bo typowy program ma zmienny stan (np. w bazie) i do tego realizuje różne procedury biznesowe z efektami. Co o tym myślisz?

overcq
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 402
0
again napisał(a):

Języki programowania są mniej lub bardziej funkcyjne. Co ładnie obrazuje odpowiedź z SO: https://stackoverflow.com/a/13600858

Z tego co rozumiem, im bardziej język jest funkcyjny tym częściej stwarza okazję do tego by zapisać kod jako wyrażenie.

Zastanawiam się co dobrego z tego wynika? Czy funkcyjność pomaga, a kiedy przeszkadza?

  1. W podanej odpowiedzi (która nie jest najwyżej punktowaną ani wybraną) pomylono pojęcie wyrażenia (‘expression’) z tym, że funkcja zwraca wartość. Innymi słowy — tak jak w wypowiedzi Johna Carmacka z ‘linku’ podanego w odpowiedzi — liczą się tylko funkcje “pure”. Pierwsza odpowiedź do tamtego pytania (wybrana) jest odpowiedniejsza.
  2. Programowanie funkcyjne to pewien abstrakt, który umożliwia łatwo maszynowo zrozumieć, co ma być obliczone, i wykorzystać do tego celu gotowe złożone komponenty sprzętowe. Abstrakt, ponieważ każdy program wykonuje się jako program proceduralny. I ewentualne kopiowanie danych jest tylko w obszarze tego abstraktu, w rzeczywistym wykonaniu proceduralnym, po kompilacji żadnego kopiowania nie musi być, zależnie od przeznaczenia kodu programu, które może być bardziej lub mniej odpowiednie do zastosowania programowania funkcyjnego.

Najlepiej rozumieć paradygmat programowania w kontekście tego, jak jest realizowany w rzeczywistości, na sprzęcie, a nie dla jakiegokolwiek ideału programowania. Dlatego języki uniwersalne mają przewagę nad tylko funkcyjnymi, ponieważ można w nich zaprogramować odpowiednio do przeznaczenia w dowolnym paradygmacie. Tylko wymagają trzymania się wyznaczonych sobie reguł.

Odnosząc się jeszcze do paradygmatu obiektowego… W programowaniu obiektowym wcale nie musi się wołać metody obiektu przez this->metoda(). Można to robić tak, jak zrobiłem w obiektowym C, przy pomocy procedur (funkcji) globalnych odpowiednio nazwanych, a tylko przekazywany jest this (np. do struktury danych). I nie powoduje to, że to nie jest programowanie obiektowe. Ponieważ wszystko jest wewnątrz abstraktu programowania obiektowego.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
again napisał(a):

Z tym flow to raczej pojedyncze funkcje niż cały program, bo typowy program ma zmienny stan (np. w bazie) i do tego realizuje różne procedury biznesowe z efektami. Co o tym myślisz?

Owszem, stan poza programem się zmienia (bazy danych, pliki, masa innych rzeczy). To że oprócz tego stan wewnątrz programu się zmienia, to dodatkowa komplikacja, której wyeliminowanie często niesie korzyści.

Oczywiście nie cały program może być funkcyjny, w pewnym momencie musisz zmienić stan, chociażby po to żeby zwrócić jakąś wartość przez standard output; ale całe moduły w programie mogą być funkcyjne. Nie wszystkie, ale niektóre, jak się to dobrze zrobi to może nawet większośc.

AG
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5
0

No na razie mamy:

  • niemodyfikowalność
  • czyste funkcje
  • thread safe

Ogólnie trudno jest to sprzedać, chyba, że większa część aplikacji to obliczenia.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
again napisał(a):

No na razie mamy:

  • niemodyfikowalność
  • czyste funkcje
  • thread safe

Ogólnie trudno jest to sprzedać, chyba, że większa część aplikacji to obliczenia.

No to są fajne zalety. Jednak nie widzę czemu by tego nie stosować?

Często można spotkać "wyznawców" FP, którzy mówią o całych językach, wzorach, monadach, wzorach matematycznych, całym tym kulcie - to z takim czymś mógłbym polemizować. Ale rada "zdefiniuj nową zmienną, zamiast modyfikować istniejącą", tam gdzie to jest względnie proste, moim zdaniem to jest spoko rada.

stivens
  • Rejestracja: dni
  • Ostatnio: dni
Wibowit
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: XML Hills
3
again napisał(a):

No na razie mamy:

  • niemodyfikowalność
  • czyste funkcje
  • thread safe

Ogólnie trudno jest to sprzedać, chyba, że większa część aplikacji to obliczenia.

mam pewien przykład na porównanie programowania funkcyjnego i imperatywnego, chociaż opis będzie trochę rozwlekły:

ostatnio robiłem poprawki do parsowania xmli w kilku apkach, w każdej było trochę inaczej, ale były dwa podejścia:

  1. wyciąganie pól xpathem (albo czymś a'la xpath), operowanie na niemutowalnym drzewie dom
  2. parsowanie oparte o parser sax https://en.wikipedia.org/wiki/Simple_API_for_XML gdzie trzeba samemu przetrzymywać stan w zmiennych mutowalnych i na ich podstawie decydować co zrobić z aktualnie przetwarzanym (tzn. zawartym w evencie z sax parsera) kawałkiem xmla

podejście z saxem jest strasznie zamotane, bo ifologia nie dość, że jest nietrywialna sama w sobie, to jeszcze trzeba wyobrazić sobie jak będzie działać, gdy sax parser będzie odpalał poszczególne eventy. eventy są różne, a w każdym jest ledwo kawałek xmla (ten aktualnie przetwarzany) podany i trzeba odpowiednio zareagować.

imperatywne podejście z parserem sax jest niby lżejsze, bo nie trzeba ładować całego ciężkiego drzewa dom do pamięci, ale za to podejście funkcyjnego z niemutowalnym drzewem dom jest znacznie prostsze do prześledzenia i zrozumienia.

podobna różnica była z przerabianiem xmli, np. rozdzielaniem ich, za pomocą lekkich imperatywnych api ze strumieniami / eventami czy ciężkich funkcyjnych z drzewem dom. podobnie jak wcześniej, w imperatywnych rozwiązaniach trzeba mieć szereg mutowalnych zmiennych na boku i robić na nich ifologię.

w pozostałych zastosowaniach (poza parsowaniem xmli) sprawa jest podobna. imperatywny kod to często ifologia zależąca od mutowalnych zmiennych, których zmiany nieraz trudno prześledzić (albo łatwo coś przegapić w gąszczu trudnych do skoordynowania zmian). w programowaniu funkcyjnym mamy niemutowalne zmienne i struktury danych (pomijam monady io, io ref, itp, skupiam się tylko na kodzie bez tzn. efektów, de facto ubocznych jeśli zostaną wykonane), więc wiadomo co skąd się bierze. przekazując niemutowalne dane do funkcji mam pewność, że nie zostaną zmodyfikowane. natomiast w imperatywce dane mogą być modyfikowane w dowolnym momencie i trzeba posiłkować się (jawnymi czy niejawnymi) kontraktami (definiującymi zachowanie kodu), żeby wiedzieć czy dane wywołanie może namieszać w poprzednio skonstruowanych danych.

niemutowalność i brak innych imperatywnych efektów ubocznych (np. rzucania wyjątków) oznacza, że można w miarę bezpiecznie refaktorować kod. problemem jest tylko wydajność - liczenie czegoś niepotrzebnie oczywiście spowalnia program. natomiast jeśli chodzi o deduplikację czy ściśle przenoszenie kawałków kodu (bez przerabiania go) to w programowaniu funkcyjnym jest bezpiecznie. w imperatywce może być tak, że funkcja pobierająca dane i zwracająca dane może przy okazji coś mutować i wtedy jest różnica czy wywołamy ją raz czy inną liczbę razy, nawet z tymi samymi parametrami - stąd usuwanie deduplikacji wymaga najpierw upewnienia się, że nie zmieniamy liczby wystąpień efektów ubocznych. przenoszenie kodu z miejsca w miejsce też może spowodować problem jeśli ten kod rzuca wyjątkami, a my je łapiemy w jednym miejscu, a w innym nie (albo reagujemy na te same wyjątki inaczej).

na wysokim poziomie podoba mi się też zarządanie efektami w programowaniu funkcyjnym (tymi de facto na samym końcu ubocznymi, ale odroczonymi, bo opakowanymi w monadę io), np. używając https://zio.dev/ (biblioteka jest w zasadzie głównie dla osób chociażby w miarę dobrze znających scalę). operowanie na monadkach io, mając już z nimi doświadczenie, jest wygodniejsze niż imperatywka, bo monady io są znacznie bardziej przewidywalne. łatwiej kontrolować zrównoleglenie, łapanie wyjątków, ponowienia itp itd niż w innych podejściach. tu musiałbym całą sprawę rozrysować (np. zrobić jakieś slajdy i omówienie do nich), żeby to było przekonujące, więc na razie brzmi dość enigmatycznie dla osób nieznających 'czysto' funkcyjnego podejścia.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
3
again napisał(a):

No na razie mamy:

  • niemodyfikowalność
  • czyste funkcje
  • thread safe

Ogólnie trudno jest to sprzedać, chyba, że większa część aplikacji to obliczenia.

Jeśli nie pracujesz w marketingu - to możesz sobie po prostu odpuścić sprzedawanie.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
4
Riddle napisał(a):

Oczywiście nie cały program może być funkcyjny, w pewnym momencie musisz zmienić stan, chociażby po to żeby zwrócić jakąś wartość przez standard output; ale całe moduły w programie mogą być funkcyjne. Nie wszystkie, ale niektóre, jak się to dobrze zrobi to może nawet większośc.

Cały program może być funkcyjny (w sensie kod - czyli program).
Kompilator, na konkretną architekturę może to potem skompilować do czegoś co będzie w środku zmieniać stan, wyświetlać napisy, a nawet robić skoki (GOTO :-)) itp. Ale z punktu widzenia programisty, czy własności kodu nic to nie zmienia - nadal jest kod czysto funkcyjny, bez efektów ubocznych.

Wibowit napisał(a):

niemutowalność i brak innych imperatywnych efektów ubocznych (np. rzucania wyjątków) oznacza, że można w miarę bezpiecznie refaktorować kod.

To jest absolutnie kluczowa własność programowania funkcyjnego... z typami. Szczególne uwydatnia się przy pracy zespołowej, utrzymaniu długoterminowym programów.

Po prostu nie musisz tyle myśleć.

Możesz się skupić na sensie biznesowym problemu dużo mniej wgryzać się w kod -> jeśli jest to kod funkcyjny to twoja niechlujność przeważnie zostanie wyłapana już na etapie kompilacji -> np. nie będą zgadzać się typy. Brak mutacji i stanów oznacza, że np. nie zrobisz dość częstego przy refaktoringu kodu, kiedy coś zmieniasz w klauzuli w istniejącej if i zapominasz, że coś podobnego trzeba zrobić w else. Albo że early return wychodzi za wcześnie, (bo w następnej linjce była zmiana jakiejś ważnej flagi itp).

Oczywiście to nie znaczy, że w ogóle nie trzeba myśleć, ale dzięki temu, że masz o wiele mniej rzeczy, które mogą się posypać piszesz o wiele swobodniej, mniejsza szansa, że coś przeoczysz. Generalnie w projektach mocno funkcyjnych (taki teraz mam w firmie) nie mamy problemu, żeby w czwartek przeorać kod jakimś refaktoringiem, a w piątek zrobić deploy.

W pewnym sensie fp daje to samo co testy (uzupełnia się z testami, a nawet powoduje czasem, że pewnych testów nie ma potrzeby robić, albo robi się je dużo łatwiej).

M3
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3
2

GO
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 358
0

Akurat nigdy nie pisałem w czystym funkcyjnym języku.

Chodź wiele AI, ml, deep learning, quantum, frameworków właśnie takie podejście implementuje, wszystko zwraca tensory.
Na których funkcyjne operacje się wykonuje, które też zwracają tensor.

Czasem można mieć wrażenie, że funkcyjne programowanie jest wolniejsze bo np. ciągle nowy obiekt jest zwracany i to zależy.
Jeśli mamy milion elementów i modyfikujemy tylko jeden, to musimy skopiować wszystkie, wtedy to jest wolniejsze niż normalnie.

Ale jeśli robimy takie mnożenia macierzy ml, deep learning, to tak czy siak modyfikujemy wszystkie pola to wtedy jest identyczny czas przy tworzeniu nowego obiektu jak przy modyfikowaniu.
Można też jednocześnie zwolnić poprzedni i w tym samym miejscu nowy postawić czyli i zachować pamięć i zrobić niemutowalny obiekt, bo poprzedni został wykasowany i usunięte do niego referencje.

TZ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 9
0

FP dla mnie bazuje na rachunku lambda i wprowadza do pewnego stopnia formalizmy z tym związane. Jest tam moim zdaniem więcej teorii związanej z logiką niźli to co w OOP wyszło. A jak się to ma w praktyce... w sumie i jedno i drugie można mapować do liczb naturalnych i na tym operować, tylko no, to jest niewygodne.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
TańczącyZBłędami napisał(a):

FP dla mnie bazuje na rachunku lambda i wprowadza do pewnego stopnia formalizmy z tym związane. Jest tam moim zdaniem więcej teorii związanej z logiką niźli to co w OOP wyszło. A jak się to ma w praktyce... w sumie i jedno i drugie można mapować do liczb naturalnych i na tym operować, tylko no, to jest niewygodne.

Brzmi jak jedna z sensownych definicji.

Jedyny problem jaki z nią mam, to to że to jest nie pragmatyczne. Kiedy nałożyć taki constraint na software (żeby korzystał z formalizmu rachunku lambda), to takie zespoły nie zyskują nic szczególnego. Nie obserwujemy mniejszej ilości bugów, łatwiejszego refaktora, szybszego dostarczania.

Jeśli w niektórych miejscach nałożyć constraint - nie modyfikuj stanu (tam gdzie da się to w miare łatwo wprowadzić), to to zyskuje pragmatyczny sens.

RO
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 4
0

Większość programistów chyba nie lubi języków funkcyjnych patrząc po ich popularności, dlaczego?

Wibowit
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: XML Hills
2
rodeo napisał(a):

Większość programistów chyba nie lubi języków funkcyjnych patrząc po ich popularności, dlaczego?

po pierwsze, programowanie funkcyjne małymi kroczkami wchodzi do głównego nurtu. np. java 8 wprowadziła te swoje streamy, które choć rozwlekłe to są dość funkcyjne. późniejsze wersje javy dorzuciły niemutowalne rekordy, pattern matching na tych rekordach, itd. nadal to mało i nie da się nazwać javy językiem w dużej mierze funkcyjnym, ale z drugiej strony pokazuje, że twórcy języków głównego nurtu doceniają programowanie funkcyjne.

po drugie, programowanie funkcyjne jest dla programistów lubiących zasady :) . programowanie funkcyjne dorzuca twarde zasady dotyczące zarządzania efektami (ubocznymi), a dla wielu programistów to może być kula u nogi, bo oni chcą tylko zaklepać byle jaki kod i iść do domu. programowanie funkcyjne jest jak zasady bhp w budowlance i serwisowaniu :) (bhp = bezpieczeństwo hamuje pracę).

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
2
.GodOfCode. napisał(a):

Czasem można mieć wrażenie, że funkcyjne programowanie jest wolniejsze bo np. ciągle nowy obiekt jest zwracany i to zależy.
Jeśli mamy milion elementów i modyfikujemy tylko jeden, to musimy skopiować wszystkie, wtedy to jest wolniejsze niż normalnie.

Bzdura wyssana z palca, albo nawet z głębszego miejsca.

Pomijają zupełnie structural sharing, kompilatory języków funkcyjnych po prostu czesto generują kod wynikowy, który najzwyczajniej w świecie robi mutacje in place itd. W kodzie możesz mieć coś co działa jak kopiowanie danych, a w praktyce zostaną wygenerowane operacje na rejestrach i żadnego kopiowania nie ma. Nawet javowy jit już sobie z tym czasem radzi.
Nie zdziwiłbym sie jakby nawet gcc już miał takie optymalizacje.

Oczywiscie są lepsze i gorsze kompilatory, i raczej nie ma idealnych. Tym niemniej nie żyjemy już w erze c lizanego, w którym kod mozna było z głowy przełozyć na assembler.

loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
0
again napisał(a):

Z tego co rozumiem, im bardziej język jest funkcyjny tym częściej stwarza okazję do tego by zapisać kod jako wyrażenie.

Wyrażenia są przereklamowane. Co kompilator ma zrobić z wyrażeniem które ma N rozwiązań?

Dlatego osobiście bardziej wolę paradygmaty odwzorowujące język naturalny, są wystarczające na 99% problemów w branży. Pozostały 1% można załatwić brute forcem.

GO
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 358
0

Jak niby w rejestrze przechowasz mln zmodyfikowanych elementów? wiadomo, że możesz sobie referencję kawałka tablicy dać, odczytać, porobić operacje, których potem nie musisz zachować to mogą sobie być wypisane na ekran i koniec, ale już jak chcesz zachować to zostaje kopia czy jakieś w stylu overriding derefencję operatora i zrobić abstrakcję nad jedną sharowaną główną strukturą i jej kawałkami zmodyfikowanymi, lub zawsze dawać copy on write jak w kernelu linuxa, przy różnych operacjach.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
3
.GodOfCode. napisał(a):

Jak niby w rejestrze przechowasz mln zmodyfikowanych elementów?

Jeśli potrzebujesz mieć milion zmodyfikowanych elementów to finalne wykonanie w fp niczym nie rozni sie od imperatywmego. Masz milion zmodyfikowanych elementów i pewnie naraz nie zmieszczą sie w rejestrach.

Chodziło mi o to, że zasadniczo fp (niemutowalne obiekty) w teorii nie tworzy żadnego istotnego narzutu. W praktyce narzut się zdaża, bo kompilatory są niedoskonałe. Najwiekszy narzut jest jednak w głowach programistów c, którzy tkwią mentalnie na turbo c i 8086.

TZ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 9
1
TańczącyZBłędami napisał(a):

FP dla mnie bazuje na rachunku lambda i wprowadza do pewnego stopnia formalizmy z tym związane. Jest tam moim zdaniem więcej teorii związanej z logiką niźli to co w OOP wyszło. A jak się to ma w praktyce... w sumie i jedno i drugie można mapować do liczb naturalnych i na tym operować, tylko no, to jest niewygodne.

Ok, rozwinięcie - podstawowy nieotypowany rachunek lambda ma 3 zasady - wprowadzenie zmiennej, lambda abstrakcje oraz aplikacje. Czyli:

  1. Jest sobie zmienna wyciągnięta z kapelusza o nazwie x lub y lub z.
  2. Robimy abstrakcje, czyli odpowiednik napisania własnej lambda funkcji. Jeśli jest sobie zmienna a, robimy np. \x -> x a. Albo np. \x -> x
  3. Aplikacja - wywołanie tejże funkcji, na ogół dokładając tylko jeden argument. Czyli (\x -> x a) b => b a. Dla (\x -> \y -> x y a) b mamy \y -> b y a

W nieotypowanym rachunku lambda można zdefiniować liczby naturalne, dodawanie, mnożenie, itp. Jest też ładny kombinator Y który to pozwala nam na zrobienie rekurencji. Tutaj bym odesłał do pracy o izomorfiźmie Curry-Howarda (to od imienia Curryego został nazwany język programowania Haskell), jest tam parę rzeczy spoko przedstawione, oprócz może wprowadzenia do naturalnej dedukcji.

Tak czy inaczej brak typów w tym podstawowym rachunku lambda był problemem, toteż wprowadzono otypowany rachunek lambda. To z kolei przeszkodziło w definiowaniu liczb naturalnych czy rekurencji, ale pozwoliło na sprowadzenie każdego wyrażenia do postaci normalnej, co miało swoje zalety. Potem powstały rozszerzenia tego rachunku lambda - dodające sum czy union types. I tu się pojawia ten cała korespondencja Curry-Howarda - ktoś zauważył że te typy w roszerzenym otypowanym rachunku lambda odpowiadają regułom przeprowadzenia dowodów matematycznych za pomocą naturalnej dedukcji (dla logiki intuicjonistycznej).

Toteż lambda abstrakcja odpowiadała zasadzie wprowadzenia implikacji w naturalnej dedukcji, aplikacja zaś odpowiadała regule modus ponens (po prostu jeśli zdania a => b i a są prawdziwe to b jest prawdziwe). Zasady dot. logicznego i odpowiadały operacji na union typach (odpowiadającym zwykłym strukturom), zaś zasady dot. lub odpowiadały operacjiom na sum typach. Negacja zaś była zdefiniowana jak w logice intuicjonistycznej, to jest ~a = a => bottom. Jako że otypowany rachunek lambda miał swoje właściwości jak redukowalność do postaci normalnej, pomogło to w pisaniu solverów do logiki matematycznej. Stąd też inferencja typów działa całkiem fajnie.

Ale klasyczny rachunek zdań to za mało jeśli chodzi o dowodzenie twierdzeń matematycznych, są jeszcze kwantyfikatory 'dla każdego' i 'istnieje'. Tutaj już mocno się nie zagłębiałem, ale na tyle ile mi wiadomo powstało kolejne rozszerzenie otypowanego rachunku lambda, tj. System F. Na wariacji tego systemu bazuje Haskell, tj. Haskell jest kompilowany do Core które jest wariacją System F - GADTsy są tam chyba zaimplementowane. Jeśli chodzi o odpowiednik do logiki matematycznej z tego co czytałem System F odpowiada logice drugiego rzędu z jakimiś niuansami dotyczącymi kwantyfikatorów.

Z tego co czytałem też jest jakiś izomorfizm pomiędzy którymś z rachunków lambda a maszyną Turinga - tj. coś co można wyrazić w jednym systemie można wyrazić za pomocą drugiego. Ale user experience trochę się różni.

A wracając do inżynierii oprogramowania - jeśli język pozwala na takie zabawy typami że możemy sobie pisać lambdy i w miarę luźno je aplikować, pozwala na funkcje wyższego rzędu - no to wspiera on paradygmat funkcyjny. Mi osobiście lepiej opisuje się data flow w językach funkcyjnych, ale z czym mam problem to z przekształcaniem grafów. Drzewa, acykliczne grafy skierowane - spoko. Ale jak pojawiają się cykle to robi się nieprzyjemnie.

No i ala gamedevowe zarządzanie stanem, też z tym miewałem problem.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.