Jaki jest dobry sposób projektowania / struktury dużych programów funkcjonalnych, zwłaszcza w Haskell?
Przeszedłem kilka samouczków (Napisz do mnie jako ulubiony program, a Real World Haskell jest na drugim miejscu) - ale większość programów jest stosunkowo niewielka i ma jeden cel. Ponadto nie uważam niektórych z nich za szczególnie eleganckie (na przykład obszerne tabele odnośników w WYAS).
Chcę teraz pisać większe programy z większą ilością ruchomych części - pozyskiwanie danych z różnych źródeł, czyszczenie, przetwarzanie na różne sposoby, wyświetlanie ich w interfejsach użytkownika, utrwalanie, komunikowanie się w sieci itp. W jaki sposób jedna najlepsza struktura takiego kodu, aby była czytelna, łatwa w utrzymaniu i dostosowywana do zmieniających się wymagań?
Istnieje dość duża literatura zajmująca się tymi pytaniami dla dużych programów imperatywnych zorientowanych obiektowo. Pomysły takie jak MVC, wzorce projektowe itp. Są przyzwoitymi receptami na realizację ogólnych celów, takich jak rozdzielenie obaw i ponowne wykorzystanie w stylu OO. Ponadto nowsze języki rozkazujące nadają się do refaktoryzacji w stylu „projektu w miarę dorastania”, do którego moim zdaniem nowicjusz Haskell wydaje się mniej odpowiedni.
Czy istnieje odpowiednik literatury dla Haskell? W jaki sposób najlepiej wykorzystać do tego celu zoo egzotycznych struktur kontrolnych w programowaniu funkcjonalnym (monady, strzały, aplikacje itp.)? Jakie najlepsze praktyki możesz polecić?
Dzięki!
EDYCJA (jest to kontynuacja odpowiedzi Dona Stewarta):
@dons wspomniał: „Monady przechwytują kluczowe projekty architektoniczne rodzajów”.
Myślę, że moje pytanie brzmi: jak myśleć o kluczowych projektach architektonicznych w czysto funkcjonalnym języku?
Rozważ przykład kilku strumieni danych i kilku etapów przetwarzania. Potrafię pisać modułowe parsery dla strumieni danych do zestawu struktur danych, a każdy etap przetwarzania mogę zaimplementować jako czystą funkcję. Kroki przetwarzania wymagane dla jednego elementu danych będą zależeć od jego wartości i innych ”. Po niektórych krokach powinny pojawić się skutki uboczne, takie jak aktualizacje GUI lub zapytania do bazy danych.
Jaki jest „właściwy” sposób, aby ładnie powiązać dane i kroki analizy? Można napisać dużą funkcję, która robi właściwe dla różnych typów danych. Lub można użyć monady, aby śledzić, co zostało przetworzone do tej pory i aby każdy krok przetwarzania uzyskał to, czego potrzebuje dalej od stanu monady. Lub można napisać w dużej mierze osobne programy i wysyłać wiadomości (nie podoba mi się ta opcja).
Slajdy, które podłączył, mają punkt Rzeczy, których potrzebujemy: „Idiomy do mapowania projektu na typy / funkcje / klasy / monady”. Jakie są idiomy? :)
Odpowiedzi:
Mówię o tym trochę w Inżynierii dużych projektów w Haskell oraz w projektowaniu i wdrażaniu XMonad. Inżynieria w dużej mierze polega na zarządzaniu złożonością. Podstawowymi mechanizmami strukturyzacji kodu w Haskell do zarządzania złożonością są:
System typów
Profiler
Czystość
Testowanie
Monady dla strukturyzacji
Klasy typów i typy egzystencjalne
Współbieżność i równoległość
par
do swojego programu, aby pokonać konkurencję za pomocą łatwego, składanego równoległości.Refaktor
Używaj FFI mądrze
Programowanie meta
Pakowanie i dystrybucja
Ostrzeżenia
-Wall
do utrzymywania kodu w czystości od zapachów. Możesz również spojrzeć na Agdę, Isabelle lub Catch, aby uzyskać więcej pewności. Aby sprawdzić, jak strzępią się, zobacz świetną wskazówkę , która zasugeruje ulepszenia.Dzięki tym wszystkim narzędziom możesz kontrolować złożoność, usuwając możliwie jak najwięcej interakcji między komponentami. Idealnie, masz bardzo dużą bazę czystego kodu, który jest naprawdę łatwy w utrzymaniu, ponieważ jest kompozycyjny. Nie zawsze jest to możliwe, ale warto dążyć.
Ogólnie: rozłóż jednostki logiczne systemu na możliwie najmniejsze komponenty referencyjne przezroczyste, a następnie zaimplementuj je w modułach. Globalne lub lokalne środowiska dla zestawów komponentów (lub komponentów wewnętrznych) mogą być mapowane na monady. Użyj algebraicznych typów danych, aby opisać podstawowe struktury danych. Podziel się tymi definicjami szeroko.
źródło
Don przedstawił ci większość powyższych szczegółów, ale oto moje dwa centy za zrobienie naprawdę drobiazgowych programów stanowych, takich jak demony systemowe w Haskell.
W końcu żyjesz w stosie transformatorów monadowych. Na dole jest IO. Co więcej, każdy główny moduł (w sensie abstrakcyjnym, nie w sensie moduł w pliku) mapuje swój niezbędny stan na warstwę w tym stosie. Jeśli więc masz kod połączenia z bazą danych ukryty w module, piszesz go, aby był nad typem MonadReader Connection m => ... -> m ... a wtedy funkcje bazy danych zawsze mogą uzyskać połączenie bez funkcji innych moduły muszą być świadome jego istnienia. Możesz skończyć z jedną warstwą przenoszącą połączenie z bazą danych, inną konfiguracją, trzecią różnymi semaforami i mvarami do rozwiązywania równoległości i synchronizacji, inną obsługą plików dziennika itp.
Najpierw sprawdź, jak radzisz sobie z błędami . W chwili obecnej największą słabością dla Haskell w większych systemach jest mnóstwo metod obsługi błędów, w tym kiepskich, takich jak Może (co jest złe, ponieważ nie można zwrócić żadnych informacji o tym, co poszło źle; zawsze używaj albo Zamiast, może, chyba że naprawdę oznacza tylko brakujące wartości). Dowiedz się, jak to zrobić w pierwszej kolejności, i skonfiguruj adaptery z różnych mechanizmów obsługi błędów używanych przez biblioteki i inny kod w ostatecznym. Oszczędzi ci to później świata smutku.
Dodatek (wyodrębniony z komentarzy; dzięki Lii i liminalisht ) -
więcej dyskusji na temat różnych sposobów krojenia dużego programu w monady na stosie:
Ben Kolera daje świetne praktyczne wprowadzenie do tego tematu, a Brian Hurt omawia rozwiązania problemu wprowadzania
lift
akcji monadycznych w niestandardową monadę. George Wilson pokazuje, jakmtl
pisać kod, który działa z dowolną monadą, która implementuje wymagane typy czcionek, a nie z niestandardowym typem monady. Carlo Hamalainen napisał kilka przydatnych notatek podsumowujących przemówienie George'a.źródło
lift
akcji monadycznych w niestandardową monadę. George Wilson pokazuje, jakmtl
pisać kod, który działa z dowolną monadą, która implementuje wymagane typy czcionek, a nie z niestandardowym typem monady. Carlo Hamalainen napisał kilka przydatnych notatek podsumowujących przemówienie George'a.Projektowanie dużych programów w Haskell nie różni się tak bardzo od robienia tego w innych językach. Programowanie w dużej mierze polega na rozbiciu problemu na możliwe do opanowania części i na tym, jak je połączyć; język implementacji jest mniej ważny.
To powiedziawszy, w dużym projekcie miło jest wypróbować system typów, aby upewnić się, że możesz dopasować swoje elementy tylko w prawidłowy sposób. Może to obejmować typy nowego typu lub fantomowe, aby sprawiać wrażenie, że rzeczy tego samego typu są różne.
Jeśli chodzi o refaktoryzację kodu w miarę postępów, czystość jest wielkim dobrodziejstwem, więc staraj się zachować jak najwięcej kodu w czystości. Czysty kod jest łatwy do refaktoryzacji, ponieważ nie ma ukrytej interakcji z innymi częściami twojego programu.
źródło
Dzięki tej książce nauczyłem się strukturalnego programowania funkcjonalnego po raz pierwszy . Być może nie jest to dokładnie to, czego szukasz, ale dla początkujących w programowaniu funkcjonalnym może to być jeden z najlepszych pierwszych kroków do nauki konstruowania programów funkcjonalnych - niezależnie od skali. Na wszystkich poziomach abstrakcji projekt powinien zawsze mieć jasno ułożone struktury.
Rzemiosło programowania funkcjonalnego
http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/
źródło
where beginner=do write $ tutorials `about` Monads
)Obecnie piszę książkę o tytule „Projektowanie funkcjonalne i architektura”. Zapewnia pełny zestaw technik tworzenia dużej aplikacji przy użyciu czysto funkcjonalnego podejścia. Opisuje wiele funkcjonalnych wzorów i pomysłów podczas budowania podobnej do SCADA aplikacji „Andromeda” do kontrolowania statków kosmicznych od zera. Moim podstawowym językiem jest Haskell. Książka obejmuje:
Można zapoznać się z kodem do książki tutaj , a „Andromeda” kod projektu.
Spodziewam się, że ukończę tę książkę pod koniec 2017 roku. Do tego czasu możesz przeczytać mój artykuł „Projektowanie i architektura w programowaniu funkcjonalnym” (Rus) tutaj .
AKTUALIZACJA
Udostępniłem moją książkę online (pierwsze 5 rozdziałów). Zobacz post na Reddit
źródło
Warto wspomnieć o blogu Gabriela Skalowalne architektury programów .
Często uderza mnie, że pozornie elegancka architektura często wypada z bibliotek, które wykazują to miłe poczucie jednorodności, w sposób oddolny. W Haskell jest to szczególnie widoczne - wzorce, które tradycyjnie byłyby uważane za „architekturę odgórną”, są zwykle rejestrowane w bibliotekach takich jak odgórną mvc , Netwire i Cloud Haskell . To znaczy, mam nadzieję, że ta odpowiedź nie będzie interpretowana jako próba zastąpienia innych w tym wątku, tylko że wybory strukturalne mogą i powinny być idealnie oderwane w bibliotekach przez ekspertów z dziedziny. Moim zdaniem prawdziwą trudnością w budowaniu dużych systemów jest ocena tych bibliotek pod kątem ich „dobroci” architektonicznej w porównaniu do wszystkich twoich pragmatycznych obaw.
Jak liminalisht wspomina w komentarzach, Wzorzec projektowania kategorii jest kolejnym postem Gabriela na ten temat, w podobnym tonie.
źródło
Uważam, że artykuł „ Nauczanie architektury oprogramowania za pomocą Haskell ” (pdf) Alejandro Serrano jest przydatny do myślenia o wielkoskalowej strukturze w Haskell.
źródło
Być może najpierw musisz cofnąć się o krok i pomyśleć, jak przełożyć opis problemu na projekt. Ponieważ Haskell ma tak wysoki poziom, może uchwycić opis problemu w postaci struktur danych, działań jako procedur i czystej transformacji jako funkcji. Masz projekt. Programowanie rozpoczyna się od skompilowania tego kodu i znalezienia w kodzie konkretnych błędów dotyczących brakujących pól, brakujących instancji i brakujących transformatorów monadycznych, ponieważ na przykład wykonujesz dostęp do bazy danych z biblioteki, która potrzebuje określonej monady stanu w ramach procedury IO. I voila, jest program. Kompilator karmi twoje szkice mentalne i zapewnia spójność projektowania i rozwoju.
W ten sposób od samego początku korzystasz z pomocy Haskell, a kodowanie jest naturalne. Nie chciałbym robić czegoś „funkcjonalnego”, „czystego” lub wystarczająco ogólnego, jeśli to, co masz na myśli, jest konkretnym zwykłym problemem. Myślę, że nadmiar inżynierii jest najbardziej niebezpieczną rzeczą w IT. Sytuacja wygląda inaczej, gdy problemem jest stworzenie biblioteki, która wyodrębnia zestaw powiązanych problemów.
źródło