Inżynieria oprogramowania, jak się ją obecnie uczy, koncentruje się całkowicie na programowaniu obiektowym i „naturalnym” obiektowym spojrzeniu na świat. Istnieje szczegółowa metodologia opisująca sposób przekształcenia modelu domeny w model klasy z kilkoma krokami i wieloma artefaktami (UML), takimi jak diagramy przypadków użycia lub diagramy klas. Wielu programistów zinternalizowało to podejście i ma dobry pomysł na temat projektowania aplikacji obiektowych od zera.
Nowym hype jest programowanie funkcjonalne, którego uczy wiele książek i samouczków. A co z funkcjonalną inżynierią oprogramowania? Czytając o Lisp i Clojure, doszedłem do dwóch interesujących stwierdzeń:
Programy funkcjonalne są często opracowywane oddolnie zamiast odgórnie („On Lisp”, Paul Graham)
Funkcjonalni programiści używają map, w których OO-programiści używają obiektów / klas („Clojure for Java Programmers”, wykład Rich Hickley).
Jaka jest zatem metodologia systematycznego (opartego na modelu?) Projektowania aplikacji funkcjonalnej, tj. W Lisp lub Clojure? Jakie są typowe kroki, jakich artefaktów używam, jak zamapować je z obszaru problemu na obszar rozwiązania?
Odpowiedzi:
Dzięki Bogu, że inżynierowie oprogramowania nie odkryli jeszcze programowania funkcjonalnego. Oto kilka podobieństw:
Wiele „wzorców projektowych” OO jest wychwytywanych jako funkcje wyższego rzędu. Na przykład wzorzec gościa jest znany w świecie funkcjonalnym jako „pasowanie” (lub jeśli jesteś sprytnym teoretykiem, „katamorfizmem”). W językach funkcjonalnych typami danych są głównie drzewa lub krotki, a każdy typ drzewa ma z sobą naturalny katamorfizm.
Te funkcje wyższego rzędu często mają pewne prawa programowania, zwane także „wolnymi twierdzeniami”.
Programiści funkcjonalni używają diagramów znacznie mniej obciążająco niż programiści OO. Znaczna część tego, co jest wyrażone na diagramach OO, jest zamiast tego wyrażona w typach lub w „podpisach”, które należy traktować jako „typy modułów”. Haskell ma również „klasy typów”, które są trochę jak typ interfejsu.
Ci funkcjonalni programiści, którzy używają typów, ogólnie myślą, że „gdy już poprawnie dopasujesz typy, kod praktycznie sam się pisze”.
Nie wszystkie języki funkcjonalne używają wyraźnych typów, ale książka How To Design Programs , doskonała książka do nauki Scheme / Lisp / Clojure, w dużej mierze opiera się na „opisach danych”, które są ściśle powiązane z typami.
Każda metoda projektowania oparta na abstrakcji danych działa dobrze. Zdarza mi się myśleć, że jest to łatwiejsze, gdy język ma wyraźne typy, ale działa nawet bez niego. Dobrą książką na temat metod projektowania abstrakcyjnych typów danych, którą łatwo przystosować do programowania funkcjonalnego, jest Abstrakcja i specyfikacja w rozwoju programu autorstwa Barbary Liskov i Johna Guttaga, pierwsze wydanie. Liskov częściowo zdobył nagrodę Turinga za tę pracę.
Inną metodologią projektowania, która jest unikalna dla Lisp, jest ustalenie, które rozszerzenia językowe byłyby przydatne w dziedzinie problemów, w której pracujesz, a następnie użycie makr higienicznych w celu dodania tych konstrukcji do twojego języka. Dobrym miejscem do zapoznania się z tego rodzaju projektami jest artykuł Matthew Flatt Creating Languages in Racket . Artykuł może znajdować się za zaporą. Możesz także znaleźć bardziej ogólny materiał na temat tego rodzaju projektu, wyszukując termin „język osadzony specyficzny dla domeny”; po konkretne porady i przykłady poza tym, co obejmuje Matthew Flatt, prawdopodobnie zacznę od Grahama On Lisp lub może ANSI Common Lisp .
Typowe kroki:
Zidentyfikuj dane w programie i operacje na nim oraz zdefiniuj abstrakcyjny typ danych reprezentujący te dane.
Zidentyfikuj typowe działania lub wzorce obliczeń i wyrażaj je jako funkcje wyższego rzędu lub makra. Spodziewaj się, że zrobisz ten krok w ramach refaktoryzacji.
Jeśli używasz pisanego języka funkcjonalnego, często i często używaj sprawdzania typów. Jeśli korzystasz z Lisp lub Clojure, najlepszą praktyką jest najpierw napisanie kontraktów funkcyjnych, w tym testów jednostkowych - jest to rozwój oparty na testach do maksimum. I będziesz chciał użyć dowolnej wersji QuickCheck, która została przeniesiona na twoją platformę, która w twoim przypadku wygląda na to, że nazywa się ClojureCheck . Jest to niezwykle potężna biblioteka do konstruowania losowych testów kodu korzystającego z funkcji wyższego rzędu.
źródło
W przypadku Clojure zalecam powrót do starego, dobrego modelowania relacyjnego. Out of Tarpit to inspirująca lektura.
źródło
Osobiście uważam, że wszystkie zwykłe dobre praktyki opracowywania OO mają zastosowanie także w programowaniu funkcjonalnym - z kilkoma drobnymi poprawkami, aby uwzględnić funkcjonalny światopogląd. Z punktu widzenia metodologii tak naprawdę nie trzeba robić nic zasadniczo innego.
Moje doświadczenie pochodzi z przeniesienia się z Javy do Clojure w ostatnich latach.
Kilka przykładów:
Poznaj swoją domenę biznesową / model danych - równie ważne, czy zamierzasz zaprojektować model obiektowy, czy stworzyć funkcjonalną strukturę danych z zagnieżdżonymi mapami. Pod pewnymi względami FP może być łatwiejszy, ponieważ zachęca do myślenia o modelu danych osobno od funkcji / procesów, ale nadal musisz robić oba.
Orientacja na usługi w projektowaniu - faktycznie działa bardzo dobrze z perspektywy FP, ponieważ typowa usługa jest tak naprawdę tylko funkcją z pewnymi efektami ubocznymi. Myślę, że „oddolne” spojrzenie na rozwój oprogramowania, które czasami jest propagowane w świecie Lisp, w rzeczywistości jest po prostu dobrymi zorientowanymi na usługi zasadami projektowania API pod inną postacią.
Test Driven Development - działa dobrze w językach FP, a czasem nawet lepiej, ponieważ czyste funkcje nadają się bardzo dobrze do pisania jasnych, powtarzalnych testów bez potrzeby konfigurowania środowiska z pełnym stanem. Możesz także zbudować osobne testy w celu sprawdzenia integralności danych (np. Czy ta mapa zawiera wszystkie klucze, których oczekuję, aby zrównoważyć fakt, że w języku OO definicja klasy wymusiłaby to dla ciebie w czasie kompilacji).
Prototying / iteration - działa równie dobrze z FP. Możesz nawet być w stanie prototypować na żywo z użytkownikami, jeśli bardzo dobrze radzisz sobie z budowaniem narzędzi / DSL i używaniem ich w REPL.
źródło
Programowanie OO ściśle łączy dane z zachowaniem. Programowanie funkcjonalne rozdziela je. Nie masz więc diagramów klas, ale masz struktury danych, a szczególnie algebraiczne typy danych. Te typy mogą być napisane tak, aby bardzo ściśle pasowały do Twojej domeny, w tym eliminując niemożliwe wartości przez konstrukcję.
Nie ma więc o tym książek i książek, ale istnieje ugruntowane podejście, które, jak mówi przysłowie, sprawia, że niemożliwe jest reprezentowanie niemożliwych wartości.
W ten sposób możesz dokonać szeregu wyborów dotyczących reprezentowania określonych typów danych jako funkcji, a odwrotnie, reprezentowania niektórych funkcji jako połączenia typów danych, aby uzyskać np. Serializację, ściślejszą specyfikację, optymalizację itp. .
Następnie, biorąc pod uwagę to, piszesz funkcje na swoich reklamach, tak że ustanawiasz jakąś algebrę - tj. Istnieją stałe prawa, które obowiązują dla tych funkcji. Niektóre mogą być idempotentne - to samo po wielu aplikacjach. Niektóre są skojarzone. Niektóre są przechodnie itp.
Teraz masz domenę, nad którą masz funkcje, które komponują zgodnie z dobrze zachowanymi przepisami. Prosta wbudowana DSL!
Aha, a biorąc pod uwagę właściwości, możesz oczywiście napisać ich automatyczne losowe testy (ala QuickCheck) .. i to dopiero początek.
źródło
Projektowanie obiektowe to nie to samo, co inżynieria oprogramowania. Inżynieria oprogramowania ma związek z całym procesem przejścia od wymagań do działającego systemu, na czas i przy niskim wskaźniku defektów. Programowanie funkcjonalne może różnić się od OO, ale nie eliminuje wymagań, wysokiego poziomu i szczegółowych projektów, weryfikacji i testowania, metryk oprogramowania, szacunków i wszystkich innych „inżynierii oprogramowania”.
Ponadto programy funkcjonalne wykazują modułowość i inną strukturę. Twoje szczegółowe projekty muszą być wyrażone w kategoriach pojęć w tej strukturze.
źródło
Jednym z podejść jest stworzenie wewnętrznej DSL w wybranym funkcjonalnym języku programowania. „Model” jest wówczas zbiorem reguł biznesowych wyrażonych w DSL.
źródło
Zobacz moją odpowiedź na inny post:
Jak Clojure podchodzi do rozdziału obaw?
Zgadzam się, że należy napisać więcej na ten temat, jak tworzyć struktury dużych aplikacji, które wykorzystują podejście FP (plus więcej należy zrobić, aby udokumentować interfejsy oparte na FP)
źródło
Chociaż można to uznać za naiwne i uproszczone, myślę, że „przepisy projektowe” (systematyczne podejście do rozwiązywania problemów stosowane w programowaniu, jak zalecają Felleisen i in. W swojej książce HtDP ), byłyby zbliżone do tego, czego się wydaje.
Oto kilka linków:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
źródło
Niedawno znalazłem tę książkę: Funkcjonalne i reaktywne modelowanie domen
Myślę, że idealnie odpowiada twojemu pytaniu.
Z opisu książki:
źródło
Istnieje styl „obliczania programu” / „projektowania według obliczeń” związany z prof. Richardem Birdem i grupą Algebra of Programming na Uniwersytecie Oksfordzkim (Wielka Brytania), nie sądzę, aby zbyt daleko posunięte było rozważanie tej metodyki.
Osobiście, chociaż lubię pracę wykonaną przez grupę AoP, nie mam dyscypliny, by samodzielnie ćwiczyć projektowanie. To jednak moja wada, a nie kalkulacja programu.
źródło
Przekonałem się, że Behaviour Driven Development jest naturalnym rozwiązaniem dla szybko rozwijającego się kodu zarówno w Clojure, jak i SBCL. Prawdziwą zaletą korzystania z BDD za pomocą języka funkcjonalnego jest to, że zwykle piszę o wiele dokładniejsze testy jednostek ziarna, niż zwykle przy użyciu języków proceduralnych, ponieważ znacznie lepiej rozkładam problem na mniejsze części funkcjonalności.
źródło
Szczerze mówiąc, jeśli chcesz zaprojektować przepisy na programy funkcjonalne, spójrz na standardowe biblioteki funkcji, takie jak Preludium Haskella. W FP wzorce są zwykle rejestrowane przez same procedury wyższego rzędu (funkcje działające na funkcjach). Jeśli więc widać wzór, często po prostu tworzy się funkcję wyższego rzędu, aby uchwycić ten wzór.
Dobrym przykładem jest fmap. Ta funkcja przyjmuje funkcję jako argument i stosuje ją do wszystkich „elementów” drugiego argumentu. Ponieważ jest on częścią klasy typu Functor, każde wystąpienie Functora (takie jak lista, wykres itp.) Może zostać przekazane jako drugi argument do tej funkcji. Przechwytuje ogólne zachowanie zastosowania funkcji do każdego elementu drugiego argumentu.
źródło
Dobrze,
Zasadniczo wiele funkcjonalnych języków programowania jest używanych na uniwersytetach od dawna w przypadku „problemów z małymi zabawkami”.
Stają się one coraz bardziej popularne, ponieważ OOP ma trudności z „programowaniem równoległym” z powodu „stanu”. A czasem funkcjonalny styl jest lepszy w przypadku problemów takich jak Google MapReduce.
Jestem pewien, że kiedy faceci funkioanl uderzą w ścianę [spróbujcie wdrożyć systemy większe niż 1.000.000 linii kodu], niektórzy z nich pojawią się z nowymi metodologiami inżynierii oprogramowania ze słowami :-). Powinni odpowiedzieć na stare pytanie: jak podzielić system na części, abyśmy mogli „ugryźć” każdy z elementów pojedynczo? [praca iteracyjna, incerementalna i ewolucyjna] przy użyciu stylu funkcjonalnego.
Ale czy programy funkcjonalne będą wykorzystywane w tak dużych systemach? Czy staną się głównym strumieniem? To jest pytanie .
I nikt nie może przyjść z realistyczną metodologią bez wdrożenia tak dużych systemów, które brudzą mu ręce. Najpierw powinieneś zabrudzić sobie ręce, a następnie zaproponować rozwiązanie. Rozwiązania-sugestie bez „prawdziwych bólów i brudu” będą „fantazją”.
źródło