FP - wady i zalety

AG
  • Rejestracja:9 miesięcy
  • Ostatnio:8 miesięcy
  • 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?

Spine
FP to jest jednocześnie @furious programming i Free Pascal. Lepiej pisz bez skrótów :]
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:21 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10055
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ć.

edytowany 3x, ostatnio: Riddle
SL
  • Rejestracja:około 7 lat
  • Ostatnio:około 13 godzin
  • Postów:864
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:prawie 5 lat
  • Ostatnio:dzień
  • Postów:366
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


Nie chciałem być programistą jednak tego zechciał świat.
edytowany 1x, ostatnio: Escanor16
KamilAdam
A co z tymi metodami statycznymi?
overcq
Możesz potraktować te powtórki jako kolejne iteracje rozwoju myśli technicznej. ;)
AG
  • Rejestracja:9 miesięcy
  • Ostatnio:8 miesięcy
  • 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.

edytowany 1x, ostatnio: Riddle
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:21 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10055
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:ponad 6 lat
  • Ostatnio:7 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
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ść


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
Zobacz pozostałe 7 komentarzy
loza_prowizoryczna
Kod jest optymalizowany i nie jest wykonywany - a to znasz jakiś kod który się wykonuje bez inputa? Przecież wyrażenie matematyczne bez wprowadzenia wartości to tylko relacja symboli i operacji na nich. I żeby uściślić - imperatywna nieskończona pętla JEST inputem.
KamilAdam
Ale ja nie mówię o inpucie. Ja mówię o outpucie z funkcji, rezultacie funkcji. Jak zgubisz wartość typu IO () zwracane z funkcji IO.putStr("Hello") to ten kod się nigdy nie wykona. Wiem bo mi się zgubiło i mi się nie wykonało
loza_prowizoryczna
Eee, no i? Jeśli zdefiniuję sobie f(x) = 1 to to znaczy że to 1 ma się pojawić jeśli tego iksa braknie? Co ty dziś piłeś? :)
KamilAdam
Ja mówię cały czas o Haskellu i wartościach zwracanych, a nie wiem o czym ty mówisz. To tu zacząłeś mówić o inputach, ja o nie nie pytałem
loza_prowizoryczna
To tu zacząłeś mówić o inputach, ja o nie nie pytałem - TO może inaczej. Jak w Haskellu zgubisz IO, nie złączysz go z zadnym innym IO i ostatecznie nie zwrócisz z maina to ten kot zwyczajnie się nie wykona. - ja rozumiem, polski to trudny język ale kontekst to nawet ChatGPT pamięta dłużej :D
AG
  • Rejestracja:9 miesięcy
  • Ostatnio:8 miesięcy
  • 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:prawie 7 lat
  • Ostatnio:około 6 godzin
  • Postów:370
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.


Nie znam się, ale się wypowiem.
Wizytówka
joh­nny_Be_go­od jest mistrzem ‘eskejpowania’ i osadzania.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:21 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10055
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.

edytowany 1x, ostatnio: Riddle
AG
  • Rejestracja:9 miesięcy
  • Ostatnio:8 miesięcy
  • 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
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:21 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10055
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.

Zobacz pozostałe 8 komentarzy
stivens
To byl sarkazm
AG
stivens
Raz, że to działa gdy lista trzyma wyłącznie niemodyfikowalne prymitywy, - BTW. po pierwsze to nie musza byc prymitywy. Klasy tez moga byc niemutowalne. A po drugie nawet jesli formalnie te itemy by byly mutowalne, to dopoki ich faktycznie nie mutujesz to wciaz to dziala jako tako
stivens
Jezyki z dobrym wsparciem dla FP, to zreszta maja jakas forme copy. updatedFoo = foo.copy(bar = updatedBar)
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:42 minuty
2

λλλ
edytowany 1x, ostatnio: stivens
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
overcq
Nie wiem, czy to jest przykład programowania funkcyjnego przeciwko programowaniu proceduralnemu. Raczej – przykład odpowiedniej do zastosowania organizacji danych. W dokumencie ‘xml’ może być dowolna kolejność, więc najlepiej załadować cały do pamięci lub przynajmniej cały fragment obejmujący przetwarzane dane. Nie wiem, jak gwarantowanie byłeś w stanie przetworzyć ‘xml’ po kawałku.
Wibowit
'Nie wiem, jak gwarantowanie byłeś w stanie przetworzyć ‘xml’ po kawałku.' - coś nie po polsku napisałeś, więc się domyślę ocb. ten parser emitujący eventy parsuje dokument liniowo i tak samo liniowo są emitowane eventy. z drugiej strony, sam dokument może mieć elementy w różnej kolejności, ale to trzeba właśnie załatwić ifologią, która n.p. jest w stanie przetworzyć podelementy (szukanego elementu) w dowolnej kolejności, a przy wychodzeniu z szukanego elementu (tag typu '</tag>') robi kolejną ifologię opierając się na całości pobocznych mutowalnych danych.
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:minuta
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
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.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
AG
Nie czytaj dosłownie, po prostu chciałbym to lepiej zrozumieć. Fajnie było by zobaczyć "jakie to proste" niż rzucanie haseł trudnych w przyswojeniu, pokazujących jak wiele ktoś wie o FP.
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:minuta
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
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).


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
AG
To co piszesz to jest bardziej kwestia stosowania typów niż FP. FP może być bez typów i wtedy nie pisałbyś tak jak piszesz, co nie?
jarekr000000
@again: typy + fp. Same typy bez FP dają o dużo, dużo mniej (np. te błędy z ifami/early return się zdarzają). FP bez typów istnieje i też ma część zalet, ale nie wszystkie (trzeba pisać o wiele wiecej testów).
M3
  • Rejestracja:9 miesięcy
  • Ostatnio:8 miesięcy
  • Postów:3
2

edytowany 2x, ostatnio: Maciek322
Riddle
@Maciek322: Szanuję. Dobry materiał.
GO
  • Rejestracja:11 miesięcy
  • Ostatnio:4 miesiące
  • 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.

LukeJL
Zależy od struktury danych. Te milion elementów może stosować structural sharing i jakby w takim drzewie być. Że jak zmieniasz jeden element to masz jakąś tam podmiankę i referencję do reszty. kiedyś oglądałem o tym prezentację na YT (wpisujesz immutable data structures w YT to ci znajduje ileś takich prezentacji)
GO
@LukeJL: nie szukaj. To już też kombinowanie pod dany typ struktury, w kernelu linuxa jest tak, że dopóki nie ma zapisu to nie robi się kopi, czyli kopia procesu jakiś fork dopiero jak coś chce zmienić to pamięć przy próbie zapisu też zostaje zforkowana, a dopóki nie jest naruszona cały czas się korzysta z tego samego egzemplarza pamięci fizycznej strony. Też z takimi podmiankami to się wydajność zmniejszy, jeśli byśmy trzymali referencję do elementów, które zmodyfikowaliśmy. I też testy jak do tego zrobić, żeby potem ogarnąć kiedy referencje do głównego obiektu się skończą
GO
Ogólnie z tymi stukturami sharowanymi to ich implementacja wygląda na mega nieciekawą, mamy tablicę i najpierw sprawdzam w hashmapie czy nie istnieje zmiana danego elementu jeśli nie to dopiero sprawdzam w tablicy element. Nie widzę jak sprawniej coś takiego zaimplementować.
GO
chodź teraz mi na banie weszły optymalizacje na poziomie procesora, da się dwie instrukcje na raz wywołać jeśli są niezależne jedna może odczytywać tablicę, a druga tą w hashmapie i w następnym warunku zdecydować co zwrócić, ale i tak wolniej to wyjdzie niż zwykłe wywołanie. Ewentualnie dwie tablice zamiast hashmapy gdzie puste elementy jakieś nulle to znak, że trzeba szukać w drugiej tablicy wartości, a jak jest to tutaj, ale wtedy zużycie pamięci jest takie same jak przy kopiowaniu i gorzej działa.
TZ
  • Rejestracja:około rok
  • Ostatnio:około 2 miesiące
  • Postów:7
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.

lion137
Mógłbyś rozwinąć?
loza_prowizoryczna
Pewnie chodzi mu że operator przypisania wygląda tak := a nie tak =
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:21 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10055
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:8 miesięcy
  • Ostatnio:8 miesięcy
  • Postów:4
0

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

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
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ę).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Zobacz pozostałe 8 komentarzy
Wibowit
@stivens: tzn. może inaczej napiszę jeszcze. monady io uławiają pisanie generycznego kodu, np. do ponawiania, obsługi wyjątków, zrównoleglania, itp itd, ale sama modularyzacja kodu biznesowego usianego monadami io nie różni się jakoś mocno od modularyzacji oopowej imperatywki. a przynajmniej jak do tej pory nie uświadczyłem jakiejś dużej różnicy. z drugiej strony nadal mam względnie 'małe' doświadczenie w używaniu monad io, tzn. jestem w stanie ich swobodnie używać, ale jak do tej pory za mało pisałem, żeby natknąć się na szereg sytuacji, w których robią taką różnicę.
stivens
ale to nie chodzi o monady, a o taka "pierdołę" jak np. to ze nie zepsujesz kolekcji dodajac/odejmujac itemy
Wibowit
@stivens: ale to nie chodzi o monady, a o taka "pierdołę" jak np. to ze nie zepsujesz kolekcji dodajac/odejmujac itemy - hmm, w oopowej imperatywce czasem się zdarza np. tak, że ni z tego ni z owego (tzn. przecząc intuicji) dane, które przekazuję do metody są modyfikowane, ale dla mnie to taka wtfowa partyzantka, że ustrzegam się tego nawet jak robię imperatywkę. albo inaczej mowiąc: tego typu wtfy muszą być bardzo zlokalizowane w kodzie (tzn. trzymane razem, obok siebie, żeby od razu było widać taką partyzantkę), żeby było to dla mnie akceptowalne.
stivens
tego typu wtfy muszą być bardzo zlokalizowane w kodzie (tzn. trzymane razem, obok siebie, żeby od razu było widać taką partyzantkę), żeby było to dla mnie akceptowalne - o widzisz, wlasnie to mialem na mysli :D
ToTomki
DDD jest wspaniałe w Scali i w przetwarzaniu danych, chociaż możliwe że to co ja rozumiem przez DDD może być czymś innym niż postrzeganie reszty świata xd
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:minuta
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
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.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
loza_prowizoryczna
nawet gcc już miał takie optymalizacje - LLVM z pewnością ma. Niekiedy Swiftowe strucle potrafią fruwać jako reference-type pod spodem.
loza_prowizoryczna
  • Rejestracja:ponad 2 lata
  • Ostatnio:14 minut
  • Postów:1593
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.


Przetrzyma wszystko
GO
  • Rejestracja:11 miesięcy
  • Ostatnio:4 miesiące
  • 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:ponad 8 lat
  • Ostatnio:minuta
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
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.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
GO
Szczerze nie lubię jak wymyślisz sobie w głowie jakiegoś chochoła, z którym potem dzielnie walczysz, jak kompilator widzi, że nada struktura nie jest nigdzie wykorzystywana, bo tak najczęściej przebiega wykonanie programu jakiegokolwiek to może sobie czy to adres danej struktury przekopiować do nowego obiektu i udawać, że to nowy obiekt, jest to optymalne, ale też zależy od zachowania programisty jak będzie chciał mieć stan jakiejś struktury w czasie t, to koniec końców kopiowanie użwając vektorowych operacji lub w tle i tak wyjdzie szybciej, niż optymalizacje z pod struktu
TZ
  • Rejestracja:około rok
  • Ostatnio:około 2 miesiące
  • Postów:7
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.

edytowany 1x, ostatnio: TańczącyZBłędami
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)