Nie rozumiem sensu programowania funkcyjnego

2

I podobnie jak w wątku poniżej nie chcę się kłócić, że "funkcyjne jest be", tylko naprawdę nie rozumiem.

Ostatnio mamy wielki trend który głosi, że "Programowanie funkcyjne jest dużo lepsze niż nie-funkcyjne". A w szczególności - lepsze niż wszystko, co imperatywne.

Niech im będzie. Ale dla mnie programowanie funkcyjne jak i argumentacje za nim sa naprawdę trudne do przełknięcia.

Po pierwsze: Dla mnie osobiście jest ono bardzo nieintuicyjne.

Ktoś na reddicie twierdził, że nieintuicyjność FP być może wypływa tylko z tego, że ludzie od początku są szkoleni do myślenia imperatywnego. Gdyby wyrzucić języki imperatywne ze szkół i zastąpić je funkcyjnymi, to myślenie funkcyjne byłoby równie intuicyjne.

Ja natomiast osobiście odnoszę wrażenie, że po prostu informatyka byłaby jeszcze bardziej znienawidzona przez większość uczniów.

Wydaje mi się, że dla człowieka naturalne jest myślenie czasownikami i w kategoriach zmieniającego się stanu.

Imperatywnie: Piszę na kartce zdanie Ala ma kota pod akapitem, który napisałem wcześniej.

Funkcyjnie: Niech k będzie dowolną kartką. k' jest kartką taką, że cały tekst napisany na k' jest identyczny z tekstem napisanym na k, a dodatkowo na k' jest jeszcze zdanie Ala ma kota poniżej jakiegokolwiek innego tekstu.

Nawet pisze się koślawo, co wskazuje, że język naturalny nie powstał do tego rodzaju opisu (bo ludzie tak nie myślą). Językiem ojczystym będzie tu raczej język matematyczny: k' = ιk∈K. tekst(k') = tekst(k) || "Ala ma kota". Tutaj dopiero to wygląda naturalnie; ale ludzie nie posługują się takim językiem na co dzień.

Gdzie tu są problemy? (a) Wyrzucamy czasowniki, bo one ze swej natury modyfikują stan; zamiast tego mamy myśleć deklaratywnie, a więc opisywać co czym jest, a nigdy: co się z czym odbywa. Jednak ludzie myślą czasownikami. (b) Co za tym idzie: Tworzymy nową kartkę. (WTF? Jak piszę na kartce to nie pojawia mi się stosik kartek po każdym napisanym słowie?) Wyrzucenie czasowników wywołuje eksplozję rzeczowników, wskutek czego zdawałoby się jeden i ten sam obiekt nagle opisywany jest wieloma rzeczownikami, a tak w ogóle to są to zupełnie różne obiekty.

Jednak tak nie jest w rzeczywistym życiu, skąd ludzie czerpią intuicje. Krojenie marchewki nie tworzy nowej marchewki. Włączenie telewizora nie powoduje, że w pokoju telewizor włączony zaczyna stać obok identycznego, ale wyłączonego.

Skądinąd zastanawiam się, co by było, gdyby przeprowadzić takie badanie: Wziąć tzw. "zwykłego człowieka" i programistę funkcyjnego i poprosić o wypowiedź na ten sam temat. Zanotować, jaka była u kogo proporcja rzeczowników do czasowników (wyjąwszy "być" i "mieć") oraz proporcja czasowników "być" i "mieć" do innych czasowników. Mam podejrzenia, że programista funkcyjny nawet na co dzień używałby mniej czasowników. Jestem ciekaw, co by z takiego badania wyszło. Bo jeśli moje przypuszczenia są prawdziwe - to jakby potwierdzało to, że bycie programistą funkcyjnym wymaga bardzo głębokich zmian w intuicji.

Po drugie: Współdzielony zmieniający się stan bywa wygodny!!

Na JPP na studiach był prikaz, by napisać interpreter wymyślonego przez siebie języka w Haskellu. To mnie zmusiło, by tego Haskella wreszcie spróbować.

Jakoś nie byłem zachwycony.

Wymyśliłem sobie język (imperatywny, ale z kilkoma nowatorskimi pomysłami). Tak - jest monada State. Pomocna przy takiej zabawie. Wśród licznych obiektów odpowiadających za stan mamy np. listę scope'ów. W moim języczku akurat nie było eksplozji możliwych dowiązań do tego samego scope'u, ale wciąż jednak pod pewnymi warunkami można się było dostać do tego samego scope'u z kilku różnych miejsc.

To natychmiast zaczęło wymagać, by każdy scope miał swój id. Scope nie mógł być dowiązany bezpośrednio do nazwy zmiennej - nie, nazwa zmiennej miała przechowywać id swojego scope'a.

Zamiast zatem prostego przejścia ze zmiennej do scope'a: Pobranie id, pobranie scope'a o tym id ze stanu przechowywanego w monadzie, zrobienie coś z tym scope'em, zapisanie nowego stanu... I tak jednolinijkowiec stawał się co najmniej trójlinijkowcem. (FP ma zmniejszać boilerplate w porównianiu do obiektówki)

I co za tym jeszcze idzie: Trzeba nagle eksplicite w ogóle deklarować tę listę scope'ów! W języku imperatywnym w ogóle takiej listy bym nie stworzył. Zamiast tego bym po prostu dołączał odpowiednie referencje.

(I co jeszcze za tym idzie, choć to już nie problem z mojego projektu, bo w moim języku z innych przyczyn i tak byłoby to niemożliwe: Problemy z automatycznym zarządzaniem pamięcią. Wyobrażam sobie, że gdybym pisał interpreter w nawet tej nieszczęsnej Javie (interpreter normalniejszego języka niż to, co wymyśliłem), to miałbym garbage collecting za darmo. Ginie ostatnia zmienna, która dowiązywała do jakiegoś scope'u? Brak jest dowiązań do scope'u, ginie i scope. W Haskellu? Ponieważ musi być centralny słownik wszystkich scope'ów, to dowiązanie do scope'a jest i wyciek pamięci gotowy.)

Co więcej: to wymuszało, by pojedyczne funkcje były naprawdę bardzo małe i robiły naprawdę tylko jedną rzecz. Dlaczego? Bo po zapisaniu zmian do monady trzeba... Pobrać dane z monady ponownie. Jeśli tego nie zrobię, to będę operował na nieaktualnych danych. To wymusza wyrzucanie każdej operacji pobierz/zapisz do osobnej funkcji, bo inaczej funkcja zaczyna być nieznośna. (Tak, pewnie ktoś zaraz powie, że "to dobrze bo i w obiektówce funkcje winny być jak najmniejsze - ciesz się, programowanie funkcyjne wymusza na tobie, co i tak powinieneś robić.)

Chodzi mi o to, że - jak dla mnie - w FP trzeba bardzo pilnować, by się stan nie rozsynchronizował. Ciągłe tworzenie nowych i jeszcze nowych obiektów łatwo może doprowadzić do tego, że skądś dojdziemy do nieaktualnych danych.

A teraz teoretycznie: Gra. Chociażby ta, którą dłubię. To wydaje się być po prostu w opór imperatywne: Jeden stworek wali drugiego więc drugi traci 20 HP. Chcę wykorzystać fakt, że można modyfikować stan globalny! Bo teraz za darmo mam już to, że to zmniejszenie się HP jest widoczne zewsząd: z listy stworków drużyny pojedynczego gracza, ze stworka, który właśnie zadał cios (referencja o wdzięcznej nazwie opponent), wszytkie efekty które zależą od HP też będą miały dostęp do aktualnego HP... W Haskellu nie miałbym tego. Gdybym po prostu zrobił dowiązania, to te dowiązania zdezaktualizowałyby się z chwilą zadania ciosu. A więc znów - centralny rejestr wszystkich stworków, jakieś sztuczne id tych stworków pewnie (aktualny przeciwnik nie jest zatem stworkiem tylko jakąś dziwną liczbą, na podstawie której trzeba dopiero pobrać stworka), i pewnie jeszcze to samo z wieloma innymi rzeczami, gdzie obecnie wykorzystuję dowiązania.

Globalny stan jest be - ale jak stworek oberwie to ja naprawdę chcę, żeby ze wszystkich innych miejsc w kodzie było widać zmniejszone HP!! A przecież w myśl paradygmatu funkcyjnego to jest właśnie tragedia, jeśli zmiana w jednym miejscu kodu propaguje się nagle do innych miejsc w kodzie. Tyle że dla mnie wyrzucenie takich propagacji wymusza komplikowanie kodu, jak opisałem na przykładach wyżej.

Jednak

Uznaję, że powyższe problemy mogą po prostu wynikać z tego, że nigdy nie nauczyłem się porządnie pisać funkcyjnie. Może po prostu próbuję przenosić praktyki, nawyki i intuicje z jęz. imperatywnych do jęz. funkcyjnych, widzę, że one jakoś nie pasują, więc odrzucam Haskella podczas gdy powinienem odrzucić te nawyki - czyli oduczyć się wszystkiego, co już umiem, i uczyć się na nowo.

Może zatem dla własnego dobra trzeba by się zmusić do pisania fukncyjnego, i powrócenia do tematu za pół roku albo i rok nawet - może po takim ćwiczeniu też bym nagle zaczął dostrzegać same zalety FP i już nigdy bym nie chciał z własnej woli pisać imperatywnie.

Nie za bardzo widzę, jak to zrobić w praktyce. W pracy mam Pythona na razie - nie chcę wykorzystywać zadań z pracy do nauki paradygmatu, z którym na razie nie za bardzo mi po drodze. Ucierpiałaby praca. Uczyć się tego sam dla siebie? Sam dla siebie piszę grę w C# - jak zacznę przepisywać ją na Haskella to chyba nigdy jej nie zrobię. Tzn będzie to może interesujące ćwiczenie pod wzgl. nauki Haskella, ale raczej będzie wymagało porzucenia marzeń o ukończeniu jej.

Tak, niby na upartego w C# DA SIĘ pisać funkcyjnie, ale znów to samo... zbyt wielkie skupienie na tym, JAK to mam robić doprowadzi do tego, że tego nie zrobię. Tym bardziej, że obecnie dostrzegam tylko upierdliwości na myśl o porzuceniu wszelkich side-effectów.

Uczyć się funkcyjnego jako trzecie zadanie, obok pracy i powyższej gry? Sorry.. doba ma 24h.

A jednak przejść nad tym do porządku dziennego nie mogę. Za wiele ludzi śpiewa peany na cześć FP. Naprawdę może być tak, że nie ucząc się go, krzywdzę wyłącznie samego siebie. I z własnego wyboru pozostaję niekompetentny.

W zasadzie nie wiem, co teraz z tym zrobić.

7

title

3
  1. Jest tak samo intuicyjne jak imperatywne, ma po prostu inną składnie i inny sposób myślenia ;) Niby pisanie deklaratywnie jest dzwine, ale jakoś SQLa ludzie używają ;)
  2. Globalny stan tylko wydaje się fajny. Jeśli robisz coś sam, to może nie zrobisz głupty (chyba, że o czymś zapomnisz), ale pracując z kimś skąd masz gwarancje tego, że przypadkiem czegoś w 'ważnym' momencie nie zmieni?

Funkcyjnie kod gry miałbyś zupełnie inaczej napisany. Nie miałbyś jednego miejsca skąd pobierasz obecny stan, tylko aktualny stan byłby jednym z parametrów wywołania ruchu, a sam ruch zwracałby nowy stan. Ten zostałby przekazany dalej i tak w kółko. Nie trzeba o nic dbać, bo wszystko działa samo ;)

2

To co opisałeś to próba przetłumaczenia imperatywnego kodu 1:1 do Haskella. Oczywiście da się to robić, ale nie o to chodzi. Przykład takiego tłumaczenia to tłumaczenie kodu C typu:

void quicksort(int *A, int len) {
  if (len < 2) return;
 
  int pivot = A[len / 2];
 
  int i, j;
  for (i = 0, j = len - 1; ; i++, j--) {
    while (A[i] < pivot) i++;
    while (A[j] > pivot) j--;
 
    if (i >= j) break;
 
    int temp = A[i];
    A[i]     = A[j];
    A[j]     = temp;
  }
 
  quicksort(A, i);
  quicksort(A + i, len - i);
}

nie na idiomatycznego Haskella typu:

qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y < x] ++ [x] ++ qsort [y | y <- xs, y >= x]

ale na pokrakę symulującą 1:1 działanie kodu w C, czyli coś a'la (pseudokod, bo w Haskellu nie pisałem wieki):

qsort [] = return [] // return bo korzystamy z typu IO
qsort [a] = return [a]
qsort (as) = do
  pivot = as[len(as) / 2]
  i <- allocVariable
  j <- allocVariable
  putValue(i, 0)
  loop(
    return true,
    do
      putValue(i, getValue(i) + 1)
      putValue(j, getValue(j) - 1),
    do
      loop
  )
... i tak dalej, mnóstwo dziadostwa z monadami IO

Teoretycznie dałoby się napisać automat zamieniający postać z C (czyli tą pierwszą) na 1:1 odpowiednik Haskellowy (czyli tak trzecia postać z mnóstwem dziadostwa). Powstały kod były czysty funkcyjnie według definicji Haskellowców, ale od razu zaczęliby krzyczeć, że nie tak się pisze w Haskellu i w ogóle jest ideologiczny zakaz takiego pisania.

FP wymaga znacznej zmiany w organizacji kodu w porównaniu do kodu imperatywnego, by miało sens. Haskell ma natomiast tę cechę, że wymusza brak efektów ubocznych nieopakowanych w typ IO. Ja tam czasem wolę zrobić sobie jakąś lokalną mutację jeśli mi to znacznie ułatwia życie. Dlatego łączę programowanie imperatywne i funkcyjne w Scali, która daje dowolność łączenia paradygmatów :]

0

Trochę hardkorowe (purystyczne?) podejście wybrałeś lecąc od razu w Haskella. Może spróbuj jak każdy, zacząć od filter/map/reduce zamiast pętli, i idąc dalej zobaczysz zalety i wady FP. Bardzo możliwe, że po prostu twoje problemy nie pasują do FP, do gier mi to średnio pasuje. No chyba że na siłę coś w stylu redux-a że jest globalny (tfu! xD) stan i akcje zmieniające go, ale pewnie ktoś się przyczepi i do tego :P

0

gdzie temat 'nie rozumiem sensu programowania proceduralnego'? :)

jako ze wiekszosc zycia pisze proceduralnie/oop to te pare epizodow w haskellu mi dalo duzo frajdy, ale mysle ze jest on strasznie upierdliwy do pisania rzeczy ktore klasycznie sie robi z mutowalnym stanem, moze zamiast tego wez jakies praktyczne zadanie ktore akurat bardziej do tego pasuje, np jak masz na cos wiecej czasu w pracy to zrob to w haskellu (jak to cos jest wylacznie dla ciebie oczywiscie). z takich rzeczy nie wymagajacych za duzo babrania sie z mutowaniem to np indekser/edytor do plikow, template engine, jakis konwerter miedzy skomplikowanymi formatami, cos do walidacji danych etc

2

Po pierwsze programowanie funkcyjne wcale nie oznacza, że język musi być deklaratywny.
Ludzie z powodzeniem programują funkcyjnie w Java a nawet w C++ czy C.

Programowanie funkcyjne ma kilka zalet:

  • łatwo napisać testy do istniejącego kodu
  • nie ma mutowanych stanów czyli nie ma problemu z race condition, więc programowanie wielowątkowe jest dużo prostsze.
  • updgrade softu może być dużo bardziej proste, co jest pokazane w klasyku Erlang the movie

Sam nie praktykuje programowania funkcyjnego, ale zdecydowanie należy spróbować, bo to bardzo poszerza horyzonty.
To jedna z tych rzeczy, na które najpierw się narzeka, a jak się już załapie to stwierdza, że jest to super rozwiązanie. Sam chętnie bym zaliczył jakiś projekt z FP.

Z drugiej strony jak widzę jeszcze: https://4programmers.net/Forum/Inzynieria_oprogramowania/329363-nie_rozumiem_sensu_programowania_obiektowego
To się zastanawiam czy to jakiś żart, wyścigi, czy wojna na paradygmaty programowania.

3

http://learnyouahaskell.com/ jest dobrym wstępem do FP.

0

Podepnę się ze swoimi wątpliwościami do tematu, bo wydaje mi się, że może to pomóc rozjaśnić problem.

Jakby co – nie znam się na programowaniu funkcyjnym, ale podoba mi się.

Zastanawiam się właśnie, jak inaczej podejść do operacji usuwania elementu z tablicy, jeśli stan tablicy ma być niezmienny (immutable). Sama operacja, np. w JavaScripcie, jest prosta – przykładowo:

const newArray = someArray.slice(1); // "newArray" zawiera wszystkie elementy "someArray" bez pierwszego

Tylko pojawia się problem: takiej operacji nie można już nazwać "usuwaniem element z tablicy", przynajmniej jak ja rozumiem "usuwanie z tablicy". Nie zostało nic usunięte z "obiektu" o nazwie "tablica" (nie mam tu na myśli "obiektu" w rozumieniu JavaScriptu, tylko ogólnie, w rozumieniu: "rzecz", "coś"). Oryginalna tablica nadal zawiera wszystkie elementy, które zawierała.

To jak inaczej takie "usuwanie" nazwać? Może w ogóle nie można nic "usunąć" (tym samym "dodać") w programowaniu funkcyjnym?

Dochodzę obecnie do wniosku, że "usuwanie" w tym rozumieniu nie istnieje w programowaniu funkcyjnym. Że istnieje coś na wzór "usuwania"; kluczowe znaczenie ma tutaj sposób myślenia o tej operacji.

Jak inaczej myśleć o "usuwaniu elementu z tablicy"? Można by tak:

  • W programowaniu nie-funkcyjnym mamy "obiekty" o nazwie "tablica", zawierające inne "obiekty"; tablice możemy zmieniać – dodawać elementy i usuwać.
  • W programowaniu funkcyjnym zaś mamy "obiekty", które są grupowane w większe "obiekty" zwane "tablicami"; w tym rozumieniu operacje "dodawania" i "usuwania" oznaczałyby po prostu inne pogrupowanie tych "obiektów".

Dobrze myślę?

2

Dziwne rzeczy.

  1. IMO myślenie tak zwanymi czasownikami to jeden z fundamentów programowania funkcyjnego.

  2. Poodejście imperatywne prowadzi do paradoksów - prędzej lub później. można je ignorować, ale są.
    Mnie się zawsze mózg skręcał jak widziałem zapis w stylu X = X+1 (chyba ktoś na głowę upad), mimo że to jeden z piewszych kodów jakie pisałem.
    Znam przypadki ludzi (2), którzy właśnie tej koncepcji nie mogli pojąć (zmiennych).

  3. No i ulegasz propagandzie:

A teraz teoretycznie: Gra. Chociażby ta, którą dłubię. To wydaje się być po prostu w opór imperatywne: Jeden stworek wali drugiego więc drugi traci 20 HP. Chcę wykorzystać fakt, że można modyfikować stan > globalny! Bo teraz za darmo mam już to, że to zmniejszenie się HP jest widoczne zewsząd: z listy stworków drużyny pojedynczego gracza, ze stworka, który właśnie zadał cios (referencja o wdzięcznej nazwie opponent), wszytkie efekty które zależą od HP też będą miały dostęp do aktualnego HP... W Haskellu nie miałbym tego.

Oczywiście masz to w haskellu:
http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-IORef.html

Różnica jest dość prosta - w Haskellu nie jest łatwo wrzucić psikusa, działania z side-effectami są jawne. W tym przypadku cokolwiek odczytuje z ioREF lub zmienia ioREf zostaje skażone przez IO. Dzięki temu wiadomo kiedy należy uważać. Kompilator przypomni.

IMO cały problem z językami i paradygmatami polega na punkcie widzenia, punkcie siedzenia i rozmiarze pisanych programów.
Jak piszesz proste skrypty do 100-1000 linijek to nie ogarniesz po kiego grzyba ludziom coś więcej niż JS, PHP, czy Python, a nawet bash.
Jak piszesz dla siebie gierki for fun to C#, Java i imperatywne podejście zwykle wystarcza.
Ale każdy rok wrzucania bugów na produkcje i rozczarowywania użytkowników powoduje, że szukasz koncepcji jak tu programować bezpieczniej, nawet jakby czasem trzeba było trochę bardziej pogłówkować.
Dla każdego praktycznie języka sa narzędzia typu sonar, lint itp. Języki funkcyjne maja część tych narzędzi niejako wbudowane.

Z tym, że nie jest tak, że fp to lek na wszystko. Problem jest taki, że podstawowy poziom dużo pomaga (niemutowalność chyba najwięcej). Ale każdy krok dalej wymaga ogarnięcia coraz większej ilości terminów ... i sam nie wiem czy to ma sens dla mainstreamu.

haskell ide

1
jarekr000000 napisał(a):

Dziwne rzeczy.

  1. IMO myślenie tak zwanymi czasownikami to jeden z fundamentów programowania funkcyjnego.

A czasowniki to nie obiektowka? W koncu w obiektowce dodajemy do listy a w FP...

(cons x current_list)

... a w FP wyrazenie wyzej ma wartosc rowna liscie z x'sem w glowie i current_list w ogonie. W szczegolnosci niczego nie dodalismy bo jezeli gdzies jest jeszcze referencja do starej listy to ona o nowej glowie nie wie. Zadnego czasownika tutaj nie ma? Wiec myslimy wartosciami a nie czynnosciami?

1

To zależy więc nie powiem, że zawsze, ale bardzo często robienie globalnego obiektu gdzie coś zmieniasz i inne obiekty od tego zależą jest słabe, bo powoduje dużo efektów ubocznych, Utrudnia to też testowanie, a komponenty stają się silnie powiązane, a powiązania między częściami systemu powinny być słabe i elastyczne. Już dużo lepiej w tym przypadku zastosować wzorzec Obserwatora - poczytaj o nim. Co do samych efektów ubocznych - nawet pisząc w obiektowym języku, jeśli unikasz niejawnych efektów ubocznych to kod sprawia mniej niespodzianek i błędów z d**y.

1

Tutaj jest to wytłumaczone przez kogoś znacznie lepiej obeznanego z tematem niż ja:
https://www.joelonsoftware.com/2006/08/01/can-your-programming-language-do-this/

0
stivens napisał(a):

A czasowniki to nie obiektowka? W koncu w obiektowce dodajemy do listy a w FP...

(cons x current_list)

... a w FP wyrazenie wyzej ma wartosc rowna liscie z x'sem w glowie i current_list w ogonie. W szczegolnosci niczego nie dodalismy bo jezeli gdzies jest jeszcze referencja do starej listy to ona o nowej glowie nie wie. Zadnego czasownika tutaj nie ma? Wiec myslimy wartosciami a nie czynnosciami?

zły przykład podałeś, bo akurat cons to skrót od construct czyli czasownik jakby jest.

Co do myślenia czasownikami to oczywiście uproszczenie, ale widzę tą różnicę w modelowaniu systemu - od czego zaczynasz - od diagramu klas/struktur/encji czy od procesów/funkcji/przepływów. (btw. można to też robić w C#/Javie nawet w stylu obiektowym i długo tak robiłem)

2

@kmph:

ad 1. bo programowanie funkcyjne często jest uczone matematycznie, a nie praktycznie jak OOP. Dlatego wiele osób ma problem z przeniesieniem elementów FP do projektów biznesowych.
ad 2. Jonh Carmack w 2013 pokazał, jak uczył się Haskella, portując Wolfa 3d na Haskella właśnie → . Później Daniel Holmes podchwycił ideę i samemu napisał W3d w haskellu https://github.com/danielholmes/wolf3d-haskell jak widać stan globalny nie jest potrzebny. Są jak widać lepsze metody.

Poza tym przeczytaj: https://medium.com/better-programming/fp-toy-7f52ea0a947e

0

@jarekr000000: chciałbym zobaczyć implementację Age of Empires 2 w haskellu... po prostu moja głowa tego nie ogarnia jak można robic tyle kopi stanów i przekazywac między sobą

0
scibi92 napisał(a):

@jarekr000000: chciałbym zobaczyć implementację Age of Empires 2 w haskellu... po prostu moja głowa tego nie ogarnia jak można robic tyle kopi stanów i przekazywac między sobą

Akurat IMO tego typu gry bardzo dobrze nadają się pod FP. Ale jak jakas bedzie to zobaczymy.
A poza tym jakich kopii....

0

chciałbym zobaczyć implementację Age of Empires 2 w haskellu... po prostu moja głowa tego nie ogarnia jak można robic tyle kopi stanów i przekazywac między sobą

@scibi92 a o Entity Component System słyszał? To jak najbardziej funkcyjne podejście do programowania gier (masz eventy, które są składane na obecny stan (komponent) i są emitowane przez systemy, entity to tylko ID, który łączy komponenty dot. tej samej jednostki). Więc jak najbardziej się da i powiedziałbym, że nawet to jest obecnie podejście "zgodnie ze sztuką".

0

FP jest dobre, bo łatwo zarządzać kodem i testować go. Także ułatwia skalowanie przez współbieżność. Moim zdaniem trudność polega na tym, że trzeba rozumieć problem, podczas gdy w programowaniu proceduralnym wystarczy wiedzieć jak go rozwiązać. Innymi słowy fp będzie trudne dopóki działasz po omacku. Gdy osiągniesz odpowiedni poziom fp jest oczywiste, choć w niektórych przypadkach zmienny stan może być pożądany choćby ze względu na wydajność. Poza tym, żeby fp sprawiało radość, język musi być odpowiedni, na przykład Java nie jest takim językiem, więc nie zwracam kijem Wisły i używam w nim znacznie mniej elementów fp.

0

Utrudnia to też testowanie, a komponenty stają się silnie powiązane, a powiązania między częściami systemu powinny być słabe i elastyczne.

Zagram adwokata diabła. Ten problem rozwiązuje dependency injection i programowanie do abstrakcji (interfejsów). Nie widzę jak miałoby to być coś co rozwiązuje wyłącznie FP. Czasem odnoszę wrażenie że takie argumenty (nie ważne czego tyczy się dyskusja) są używane byle by były, taka sztuka dla sztuki.

0
Aventus napisał(a):

Utrudnia to też testowanie, a komponenty stają się silnie powiązane, a powiązania między częściami systemu powinny być słabe i elastyczne.

Zagram adwokata diabła. Ten problem rozwiązuje dependency injection i programowanie do abstrakcji (interfejsów). Nie widzę jak miałoby to być coś co rozwiązuje wyłącznie FP. Czasem odnoszę wrażenie że takie argumenty (nie ważne czego tyczy się dyskusja) są używane byle by były, taka sztuka dla sztuki.

Tutaj bardziej chodzi o powiązania w sensie kto komu zmienia stan, a nie kto tworzy obiekty i jak szeroki jest udostępniany interfejs. W kodzie imperatywnym często spotyka się takie coś:

val x = skonstruujNowyX();
obiekt1.ustawCoś(x);
assert(obiekt2.pobierzCoś() == x.name);

Widać tutaj silne powiązanie między obiekt1, a obiekt2. W FP robienie takich powiązań jest upierdliwe i niespecjalnie pożądane (wymaga użycia typu IO), więc się tego unika i stara przedstawić logikę przetwarzania informacji jako szereg transformacji (ze starego obiektu w nowy), a nie mutacji. Oczywiście w Javce, C#, etc też można zrezygnować z mutacji na rzecz transformacji, ale tym samym wprowadzasz (przynajmniej miejscowo) FP.

1

Ciekawe, że w czasach kiedy React+Redux to najmodniejszy stack do robienia frontu, ludzie wciąż mają wątpliwości, czy można w FP efektywnie obsłużyć stan...

0
Aventus napisał(a):

Ten problem rozwiązuje dependency injection i programowanie do abstrakcji (interfejsów).

DI czy programowanie do abstrakcji nie zmienia faktu, że testowanie gdy:

b = f(a)

assert a == a

Jest zdecydowanie łatwiejsze. Oczywiście w językach obiektowych też da się to w pewien sposób ograniczyć, ale języki funkcyjne to wymuszają. I to jest fajną rzeczą.

1 użytkowników online, w tym zalogowanych: 0, gości: 1