Przez wiele lat programowania OO rozumiałem, czym są dyskryminowane związki, ale tak naprawdę nigdy ich nie przegapiłem. Niedawno zajmowałem się programowaniem funkcjonalnym w języku C # i teraz wciąż żałuję, że ich nie miałem. To mnie zaskakuje, ponieważ na pierwszy rzut oka koncepcja dyskryminowanych związków wydaje się całkiem niezależna od dychotomii funkcjonalnej / OO.
Czy jest coś nieodłącznego w programowaniu funkcjonalnym, które sprawia, że dyskryminowane związki są bardziej użyteczne niż byłyby w OO, czy też jest to, że zmuszając się do analizowania problemu w „lepszy” sposób, po prostu podniosłem swoje standardy i teraz żądam lepszego Model?
Odpowiedzi:
Zróżnicowane związki naprawdę świecą w połączeniu z dopasowaniem wzorców, w którym wybierasz różne zachowania w zależności od przypadków. Ale ten wzorzec jest zasadniczo przeciwny do czystych zasad OO.
W czystym OO różnice w zachowaniu powinny być definiowane przez same typy (obiekty) i hermetyzowane. Zatem równoważność z dopasowaniem wzorca polegałaby na wywołaniu jednej metody na samym obiekcie, który jest następnie przeciążony przez podtypy, o których mowa, w celu zdefiniowania różnych zachowań. Sprawdzanie typu obiektu z zewnątrz (co robi dopasowanie wzorca) jest uważane za antypattern.
Podstawowa różnica polega na tym, że dane i zachowanie są oddzielne w programowaniu funkcjonalnym, podczas gdy dane i zachowanie są enkapsulowane razem w OO.
To jest powód historyczny. Język taki jak C # rozwija się od klasycznego języka OO do języka z wieloma paradygmatami poprzez wprowadzanie coraz większej liczby funkcji.
źródło
List<A>
metodaB Match<B>(B nil, Func<A,List<A>,B> cons)
. Na przykład jest to dokładnie taki sam wzór, jakiego Smalltalk używa dla boolanów. W ten sposób Scala sobie z tym radzi. Używanie wielu klas jest szczegółem implementacji, którego nie trzeba ujawniać.isEmpty
metoda sprawdzająca, czy odwołanie do następnego węzła jest puste.Po zaprogramowaniu w Pascalu i Adzie przed nauczeniem się programowania funkcjonalnego, nie kojarzę dyskryminowanych związków z programowaniem funkcjonalnym.
Związki dyskryminowane są w pewnym sensie podwójnym dziedzictwem. Pierwszy pozwala łatwo dodawać operacje na stałym zestawie typów (te w unii), a dziedziczenie pozwala na łatwe dodawanie typów za pomocą stałego zestawu operacji. (Jak łatwo dodać oba, nazywa się problemem wyrażania ; jest to szczególnie trudny problem w przypadku języków z systemem typu statycznego).
Ze względu na nacisk OO na typy i podwójny nacisk na programowanie funkcjonalne na funkcje, funkcjonalne języki programowania mają naturalne powinowactwo do typów unii i oferują struktury składniowe ułatwiające ich użycie.
źródło
Techniki programowania imperatywnego, często stosowane w OO, często polegają na dwóch wzorcach:
null
aby wskazać „brak wartości” lub błąd.W paradygmacie funkcjonalnym zwykle unika się obydwu, preferując zwrócenie typu związku, który wskazuje przyczynę sukcesu / niepowodzenia lub wartość / brak wartości.
Zróżnicowane związki pasują do rachunku dla tych rodzajów związków. Na przykład w pierwszej kolejności możesz zwrócić
true
lub strukturę danych opisującą awarię. W drugim przypadku, związku, który zawiera wartość lubnone
,nil
itp drugim przypadku jest tak powszechne, że wiele języków funkcjonalne mają „może” lub „option” typ wbudowany do reprezentowania tej wartości / brak związku.Po przejściu na styl funkcjonalny z np. C # szybko znajdziesz zapotrzebowanie na te typy związków.
void/throw
inull
po prostu nie czuję się dobrze z takim kodem. A dyskryminowane związki (DU) dobrze pasują do rachunku. Tak więc odkryłeś, że chcesz ich, tak jak wielu z nas.Dobrą wiadomością jest to, że istnieje wiele bibliotek, które modelują DU w np. C # (spójrz na przykład na moją własną bibliotekę Succinc <T> ).
źródło
Typy sum byłyby na ogół mniej przydatne w głównych językach OO, ponieważ rozwiązują one podobny typ problemu jak podtypowanie OO. Jednym ze sposobów patrzenia na nie jest to, że oba obsługują
open
podtypy, ale OO polega na tym, że można dodawać dowolne podtypy do typu rodzica, a typy sum toclosed
np. Określa się z góry, które podtypy są prawidłowe.Teraz wiele języków OO łączy podtypy z innymi pojęciami, takimi jak odziedziczone struktury, polimorfizm, pisanie odniesień itp., Aby były ogólnie bardziej użyteczne. Konsekwencją jest to, że wydają się być więcej pracy, aby skonfigurować (z klas i konstruktorów i etażerka), więc raczej nie być używane do takich rzeczy jak
Result
S iOption
S i tak dalej, aż generic wpisując stały się powszechne.Powiedziałbym również, że skupienie się na relacjach w świecie rzeczywistym, których większość ludzi nauczyła się, kiedy rozpoczęli programowanie OO, np. Dog isa Animal, oznaczało, że liczba całkowita isa wynik lub błąd isa wynik wydają się nieco obce. Chociaż pomysły są dość podobne.
Co do tego, dlaczego języki funkcjonalne mogą preferować pisanie zamknięte niż pisanie otwarte, jednym z możliwych powodów jest to, że preferują dopasowanie wzorca. Jest to przydatne w przypadku polimorfizmu funkcji, ale działa również bardzo dobrze w przypadku typów zamkniętych, ponieważ kompilator może statycznie sprawdzić, czy dopasowanie obejmuje wszystkie podtypy. Może to sprawić, że język będzie bardziej spójny, chociaż nie sądzę, aby przyniosła to jakąś naturalną korzyść (mogę się mylić).
źródło
sealed
oznacza „można rozszerzyć tylko w tej samej jednostce kompilacji”, co pozwala na zamknięcie zestawu podklas w czasie projektowania.Swift chętnie stosuje dyskryminujące związki, z tym że nazywa je „wyliczeniami”. Wyliczenia są jedną z pięciu podstawowych kategorii obiektów w Swift, którymi są klasa, struct, enum, krotka i zamknięcie. Opcjonalne Swift to wyliczenia, które dyskryminują związki i są absolutnie niezbędne dla każdego kodu Swift.
Zatem założenie, że „dyskryminujące związki są związane z programowaniem funkcjonalnym” jest błędne.
źródło