Ostatnio interesowałem się niektórymi koncepcjami programowania funkcjonalnego. Od jakiegoś czasu korzystam z OOP. Widzę, jak zbudowałbym dość złożoną aplikację w OOP. Każdy obiekt wiedziałby, jak to robić. Lub cokolwiek, co robi klasa rodziców. Mogę więc po prostu powiedzieć, Person().speak()
żeby ta osoba mówiła.
Ale jak zrobić podobne rzeczy w programowaniu funkcjonalnym? Widzę, jak funkcje są pierwszorzędnymi elementami. Ale ta funkcja robi tylko jedną konkretną rzecz. Czy po prostu miałbym say()
metodę, która pływałaby i wywoływałaby ją z równoważnym Person()
argumentem, aby wiedzieć, co to za coś mówi?
Widzę więc proste rzeczy, jak zrobić OOP i obiekty w programowaniu funkcjonalnym, aby móc modulować i organizować moją bazę kodu?
Dla porównania, moim podstawowym doświadczeniem z OOP jest Python, PHP i trochę C #. Języki, na które patrzę, które mają funkcje, to Scala i Haskell. Chociaż pochylam się w stronę Scali.
Podstawowy przykład (Python):
Animal(object):
def say(self, what):
print(what)
Dog(Animal):
def say(self, what):
super().say('dog barks: {0}'.format(what))
Cat(Animal):
def say(self, what):
super().say('cat meows: {0}'.format(what))
dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')
Odpowiedzi:
Naprawdę pytasz o to, jak robić polimorfizm w językach funkcjonalnych, tj. Jak tworzyć funkcje, które zachowują się inaczej w zależności od ich argumentów.
Zauważ, że pierwszy argument funkcji jest zwykle równoważny „obiektowi” w OOP, ale w językach funkcjonalnych zwykle chcesz oddzielić funkcje od danych, więc „obiekt” prawdopodobnie będzie czystą (niezmienną) wartością danych.
Języki funkcjonalne ogólnie zapewniają różne opcje osiągania polimorfizmu:
Jako przykład, oto implementacja Twojego problemu Clojure przy użyciu wielu metod:
Zauważ, że to zachowanie nie wymaga zdefiniowania żadnych jawnych klas: zwykłe mapy działają dobrze. Funkcja wysyłania (w tym przypadku wpisz:) może być dowolną funkcją argumentów.
źródło
To nie jest bezpośrednia odpowiedź, ani też niekoniecznie 100% dokładna, ponieważ nie jestem ekspertem od języków funkcjonalnych. Ale w obu przypadkach podzielę się z Wami moim doświadczeniem ...
Mniej więcej rok temu pływałem podobną łodzią jak ty. Zrobiłem C ++ i C # i wszystkie moje projekty zawsze były bardzo obciążone na OOP. Słyszałem o językach FP, czytałem informacje w Internecie, przeglądałem książkę F #, ale nadal nie mogłem naprawdę zrozumieć, w jaki sposób język FP może zastąpić OOP lub być użyteczny ogólnie, ponieważ większość przykładów, które widziałem, były po prostu zbyt proste.
Dla mnie „przełom” przyszedł, kiedy zdecydowałem się nauczyć Pythona. Pobrałem python, potem poszedłem na stronę główną projektu euler i zacząłem robić jeden problem po drugim. Python niekoniecznie jest językiem FP i z pewnością można w nim tworzyć klasy, ale w porównaniu do C ++ / Java / C # ma o wiele więcej konstrukcji FP, więc kiedy zacząłem się nim bawić, świadomie postanowiłem nie zdefiniować klasę, chyba że absolutnie musiałem.
Interesujące w Pythonie było to, jak łatwe i naturalne było przyjmowanie funkcji i „łączenie” ich w celu tworzenia bardziej złożonych funkcji, a ostatecznie problem został rozwiązany przez wywołanie jednej funkcji.
Wskazałeś, że przy kodowaniu należy przestrzegać zasady pojedynczej odpowiedzialności i jest to absolutnie poprawne. Ale tylko dlatego, że funkcja jest odpowiedzialna za jedno zadanie, nie oznacza, że może ona wykonać absolutne minimum. W FP nadal masz poziomy abstrakcji. Więc twoje funkcje wyższego poziomu mogą nadal robić „jedną” rzecz, ale mogą delegować funkcje niższego poziomu, aby zaimplementować bardziej szczegółowe informacje o tym, jak osiągnąć tę „jedną” rzecz.
Kluczem w FP jest jednak to, że nie masz skutków ubocznych. Tak długo, jak traktujesz aplikację jako prostą transformację danych ze zdefiniowanym zestawem danych wejściowych i zestawem danych wyjściowych, możesz napisać kod FP, który osiągnie to, czego potrzebujesz. Oczywiście nie każda aplikacja będzie dobrze pasować do tej formy, ale kiedy zaczniesz to robić, będziesz zaskoczony, ile aplikacji pasuje. I tutaj myślę, że tutaj świecą Python, F # lub Scala, ponieważ dają konstrukty FP, ale kiedy musisz pamiętać swój stan i „wprowadzać efekty uboczne”, zawsze możesz polegać na prawdziwych i wypróbowanych technikach OOP.
Od tego czasu napisałem całą masę kodu Pythona jako narzędzia i inne skrypty pomocnicze do pracy wewnętrznej, a niektóre z nich zostały znacznie rozszerzone, ale pamiętając podstawowe zasady SOLID, większość tego kodu wciąż była bardzo łatwa w utrzymaniu i elastyczna. Podobnie jak w OOP, twój interfejs jest klasą i przesuwasz klasy podczas refaktoryzacji i / lub dodawania funkcjonalności, w FP robisz dokładnie to samo z funkcjami.
W zeszłym tygodniu zacząłem kodować w Javie i od tego czasu prawie codziennie przypomina mi się, że kiedy jestem w OOP, muszę implementować interfejsy, deklarując klasy metodami zastępującymi funkcje, w niektórych przypadkach mogłem osiągnąć to samo w Pythonie używając proste wyrażenie lambda, na przykład 20-30 linii kodu, które napisałem, aby przeskanować katalog, zawierałoby 1-2 wiersze w Pythonie i brak klas.
Same FP są językami wyższego poziomu. W Pythonie (przepraszam, moje jedyne doświadczenie w FP) mogłem połączyć rozumienie listy wewnątrz innego rozumienia listy z lambdami i innymi rzeczami wrzuconymi, a wszystko to byłoby tylko 3-4 liniami kodu. W C ++ mógłbym absolutnie osiągnąć to samo, ale ponieważ C ++ jest na niższym poziomie, musiałbym napisać znacznie więcej kodu niż 3-4 linie, a wraz ze wzrostem liczby linii moje szkolenie SRP zacznie działać i zacznę myślenie o tym, jak podzielić kod na mniejsze części (tj. więcej funkcji). Ale w trosce o łatwość konserwacji i ukrywanie szczegółów implementacji umieściłem wszystkie te funkcje w tej samej klasie i uczynię je prywatnymi. I oto masz ... Właśnie utworzyłem klasę, podczas gdy w pythonie napisałbym „return (.... lambda x: .. ....)”
źródło
W Haskell najbliższa jest „klasa”. Ta klasa, choć nie taka sama jak klasa w Javie i C ++ , będzie działać na to, czego chcesz w tym przypadku.
W twoim przypadku tak będzie wyglądał twój kod.
Następnie możesz mieć indywidualne typy danych dostosowujące te metody.
EDYCJA: - Przed specjalizacją powiedz Dog, musisz powiedzieć systemowi, że Pies jest zwierzęciem.
EDYCJA: - Dla Wilq.
Teraz, jeśli chcesz użyć say w funkcji say foo, będziesz musiał powiedzieć haskell, że foo może działać tylko ze Zwierzęciem.
teraz, jeśli wołasz psa do psa, szczeka, jeśli wołasz kota, miauczy.
Nie możesz teraz podać żadnej innej definicji funkcji. Jeśli powiedzmy, że jest wywołany z czymkolwiek innym niż zwierzę, spowoduje to błąd kompilacji.
źródło
Języki funkcjonalne używają 2 konstrukcji do osiągnięcia polimorfizmu:
Tworzenie z nich kodu polimorficznego jest zupełnie inne niż sposób, w jaki OOP wykorzystuje dziedziczenie i metody wirtualne. Chociaż oba mogą być dostępne w twoim ulubionym języku OOP (jak C #), większość języków funkcjonalnych (jak Haskell) podnosi je do jedenastu. Rzadko występuje funkcja, która nie jest ogólna, a większość funkcji ma funkcje jako parametry.
Trudno to wyjaśnić w ten sposób i nauka nowego sposobu będzie wymagała dużo czasu. Ale aby to zrobić, musisz całkowicie zapomnieć o OOP, ponieważ tak nie działa w funkcjonalnym świecie.
źródło
to naprawdę zależy od tego, co chcesz osiągnąć.
jeśli potrzebujesz tylko sposobu organizacji zachowania w oparciu o kryteria selektywne, możesz użyć np. słownika (tablicy skrótów) z obiektami funkcyjnymi. w pythonie może to być coś w stylu:
zauważ jednak, że (a) nie ma „wystąpień” psa lub kota i (b) będziesz musiał sam śledzić „typ” swoich obiektów.
jak na przykład:
pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]
. wtedy możesz zrobić takie zrozumienie listy jak[animals[pet[1]]['say'](pet[2]) for pet in pets]
źródło
Języki OO mogą być czasami używane zamiast języków niskiego poziomu do bezpośredniego połączenia z maszyną. C ++ Na pewno, ale nawet dla C # istnieją adaptery i tym podobne. Chociaż pisanie kodu do sterowania częściami mechanicznymi i drobnej kontroli nad pamięcią jest najlepiej utrzymywane jak najbliżej niskiego poziomu, jak to możliwe. Ale jeśli to pytanie jest związane z obecnym oprogramowaniem obiektowym, takim jak linia biznesu, aplikacje internetowe, IOT, usługi sieciowe i większość masowo używanych aplikacji, to ...
Odpowiedz, jeśli dotyczy
Czytelnicy mogą próbować pracować z architekturą zorientowaną na usługi (SOA). To znaczy, DDD, N-warstwowe, N-wielowarstwowe, sześciokątne, cokolwiek. Nie widziałem, aby aplikacja dla dużych firm efektywnie korzystała z „tradycyjnego” OO (Active-Record lub Rich-Models), jak to opisano w latach 70. i 80. w ostatniej dekadzie +. (Patrz uwaga 1)
Błąd nie dotyczy OP, ale z tym pytaniem wiąże się kilka problemów.
Podany przykład to po prostu demonstracja polimorfizmu, nie jest to kod produkcyjny. Czasami dokładnie takie przykłady są brane dosłownie.
W FP i SOA dane są oddzielone od logiki biznesowej. Oznacza to, że dane i logika nie idą w parze. Logika przechodzi do usług, a dane (modele domenowe) nie zachowują się polimorficznie (patrz uwaga 2).
Usługi i funkcje mogą być polimorficzne. W FP często przekazujesz funkcje jako parametry do innych funkcji zamiast wartości. Możesz zrobić to samo w OO Languages z typami takimi jak Callable lub Func, ale nie działa on gwałtownie (patrz Uwaga 3). W FP i SOA twoje modele nie są polimorficzne, tylko twoje usługi / funkcje. (Patrz uwaga 4)
W tym przykładzie występuje zły przypadek kodowania. Nie mówię tylko o czerwonym sznurku „pies szczeka”. Mówię też o samych modelach CatModel i DogModel. Co dzieje się, gdy chcesz dodać owcę? Musisz wejść do swojego kodu i utworzyć nowy kod? Czemu? W kodzie produkcyjnym wolałbym zobaczyć tylko AnimalModel z jego właściwościami. W najgorszym przypadku AmphibianModel i FowlModel, jeśli ich właściwości i obsługa są tak różne.
Oto, czego oczekiwałbym w obecnym języku „OO”:
Jak przechodzisz od zajęć w OO do programowania funkcjonalnego? Jak powiedzieli inni; Możesz, ale tak naprawdę nie. Chodzi o to, aby zademonstrować, że nie powinieneś nawet używać klas (w tradycyjnym znaczeniu tego słowa) podczas programowania w Javie i C #. Kiedy już zaczniesz pisać kod w architekturze zorientowanej na usługi (DDD, warstwowe, warstwowe, heksagonalne, cokolwiek), będziesz o krok bliżej do funkcjonalności, ponieważ oddzielasz swoje dane (modele domen) od funkcji logicznych (usługi).
Język OO o krok bliżej do FP
Możesz nawet pójść o krok dalej i podzielić swoje usługi SOA na dwa typy.
Opcjonalna klasa typu 1 : Wspólne usługi implementujące interfejs dla punktów wejścia. Byłyby to „nieczyste” punkty wejścia, które mogą wywoływać inne „czyste” lub „nieczyste” funkcje. Mogą to być Twoje Punkty wejścia z API RESTful.
Opcjonalna klasa typu 2 : czyste usługi logiki biznesowej. Są to klasy statyczne, które mają funkcjonalność „czystą”. W FP „Pure” oznacza, że nie ma żadnych skutków ubocznych. Nigdzie nie ustawia on wyraźnie stanu ani trwałości. (Patrz uwaga 5)
Kiedy więc pomyślisz o klasach w językach zorientowanych obiektowo, które są używane w architekturze zorientowanej na usługi, nie tylko przynosi to korzyści Twojemu kodowi OO, ale sprawia, że programowanie funkcjonalne wydaje się bardzo łatwe do zrozumienia.
Notatki
Uwaga 1 : Oryginalny, zorientowany obiektowo obiekt „Rich” lub „Active-Record” jest nadal dostępny. W przeszłości, gdy ludzie „robili to dobrze” dekadę lub więcej temu, istnieje wiele takich starszych kodów. Ostatni raz widziałem tego rodzaju kod (poprawnie wykonany) z gry wideo Codebase w C ++, w którym dokładnie kontrolują pamięć i mają bardzo ograniczoną przestrzeń. Nie wspominając, że FP i architektury zorientowane na usługi są bestiami i nie powinny brać pod uwagę sprzętu. Ale priorytetem jest możliwość ciągłej zmiany, utrzymania, zmiany wielkości danych i innych aspektów. W grach wideo i sztucznej inteligencji maszyn bardzo precyzyjnie kontrolujesz sygnały i dane.
Uwaga 2 : Modele domen nie mają zachowania polimorficznego, ani nie mają zależności zewnętrznych. Są „odizolowane”. To nie znaczy, że muszą być w 100% anemiczne. Mogą mieć wiele logiki związanej z ich budową i zmiennymi zmianami właściwości, jeśli ma to zastosowanie. Zobacz DDD „Obiekty wartości” i jednostki autorstwa Erica Evansa i Marka Seemanna.
Uwaga 3 : Linq i Lambda są bardzo powszechne. Ale kiedy użytkownik tworzy nową funkcję, rzadko używa Func lub Callable jako parametrów, podczas gdy w FP byłoby dziwnie widzieć aplikację bez funkcji zgodnych z tym wzorcem.
Uwaga 4 : Nie mylić polimorfizmu z dziedziczeniem. Model CatModel może odziedziczyć AnimalBase, aby określić, jakie właściwości zwierzę zwykle ma. Ale jak pokazuję, takie modele to Zapach Kodowy . Jeśli zobaczysz ten wzorzec, możesz rozważyć jego rozbicie i przekształcenie w dane.
Uwaga 5 : Funkcje Pure mogą (i wykonują) akceptować funkcje jako parametry. Funkcja przychodząca może być nieczysta, ale może być czysta. Do celów testowych zawsze byłby czysty. Ale w produkcji, chociaż jest traktowany jako czysty, może zawierać skutki uboczne. To nie zmienia faktu, że czysta funkcja jest czysta. Chociaż funkcja parametru może być nieczysta. Nie mylące! :RE
źródło
Możesz zrobić coś takiego… php
źródło
$whostosay
Staje się typem obiektu, który określa, co zostanie wykonane. Powyższe można zmodyfikować, aby dodatkowo zaakceptować inny parametr$whattosay
, aby mógł go użyć typ, który go obsługuje (np.'human'
).