Uczciwe ostrzeżenie, jestem nowy w programowaniu funkcjonalnym, więc mogę mieć wiele złych założeń.
Uczyłem się o typach algebraicznych. Wydaje się, że ma je wiele języków funkcjonalnych i są one dość przydatne w połączeniu z dopasowaniem wzorców. Jaki problem rozwiązują? Mogę zaimplementować pozornie (w pewnym sensie) typ algebraiczny w języku C # w następujący sposób:
public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
public T Value { get; set; }
}
var result = GetSomeValue();
if(result is None)
{
}
else
{
}
Ale myślę, że większość zgodziłaby się, że jest to draństwo programowania obiektowego i nigdy nie powinieneś tego robić. Czy więc programowanie funkcjonalne po prostu dodaje czystszą składnię, która sprawia, że ten styl programowania wydaje się mniej obrzydliwy? Czego jeszcze mi brakuje?
functional-programming
algebraic-data-type
ConditionRacer
źródło
źródło
class ThirdOption : Option{}
i podam ci miejsce, wnew ThirdOption()
którym się spodziewałeśSome
lubNone
?data Maybe a = Just a | Nothing
(odpowiednikdata Option a = Some a | None
w twoim przykładzie): nie można dodać trzeciego przypadku post-hoc. Chociaż możesz w pewien sposób emulować typy sum w języku C # w sposób pokazany przez Ciebie, nie jest to najładniejsze.Odpowiedzi:
Klasy z interfejsami i dziedziczeniem prezentują otwarty świat: każdy może dodać nowy rodzaj danych. Dla danego interfejsu mogą istnieć klasy wdrażające go na całym świecie, w różnych plikach, w różnych projektach, w różnych firmach. Ułatwiają dodawanie przypadków do struktur danych, ale ponieważ implementacje interfejsu są zdecentralizowane, trudno jest dodać nową metodę do interfejsu. Kiedy interfejs jest publiczny, jest zasadniczo zawieszony. Nikt nie zna wszystkich możliwych implementacji.
Algebraiczne typy danych są dualne, są zamknięte . Wszystkie przypadki danych są wymienione w jednym miejscu, a operacje nie tylko mogą wyczerpująco wymieniać warianty, ale są do tego zachęcane . W związku z tym napisanie nowej funkcji działającej na algebraicznym typie danych jest banalne: po prostu napisz tę cholerną funkcję. W zamian dodawanie nowych przypadków jest skomplikowane, ponieważ musisz przejrzeć w zasadzie całą bazę kodu i rozszerzyć każdą
match
. Podobnie jak w przypadku interfejsów, w standardowej bibliotece Rust dodanie nowego wariantu jest przełomową zmianą (dla typów publicznych).Są to dwie strony problemu z wyrażeniem . Algebraiczne typy danych są dla nich niepełnym rozwiązaniem, ale tak samo jest z OOP. Obie mają zalety w zależności od liczby przypadków danych, częstotliwości ich zmieniania oraz częstotliwości rozszerzania lub zmieniania operacji. (Dlatego wiele współczesnych języków zapewnia oba lub coś podobnego, albo wybiera bardziej wydajne i bardziej skomplikowane mechanizmy, które próbują objąć oba podejścia.)
źródło
To może uproszczenie, ale tak.
Wyjaśnijmy, jakie są typy danych algebraicznych (podsumowując ten drobny link z Learn you as Haskell):
twój przykład naprawdę działa tylko z pierwszym.
Być może brakuje ci dwóch podstawowych operacji, a języki funkcyjne pozwalają zbudować wszystko inne. C # ma na sobie struktury, klasy, wyliczenia, generyczne i stosy reguł rządzących tym, jak te rzeczy się zachowują.
W połączeniu z pewną składnią, która może pomóc, języki funkcjonalne mogą rozkładać operacje do tych dwóch ścieżek, zapewniając czyste, proste i eleganckie podejście do typów.
Rozwiązują ten sam problem, co każdy inny system typów: „jakie wartości można tutaj zastosować zgodnie z prawem?” - po prostu mają inne podejście do tego.
źródło
Może cię zaskoczyć informacja, że dopasowanie wzorca nie jest uważane za najbardziej idiomatyczny sposób pracy z Opcjami. Więcej informacji na ten temat można znaleźć w dokumentacji Opcji Scali . Nie jestem pewien, dlaczego tak wiele samouczków FP zachęca do takiego używania.
W większości brakuje Ci całej gamy funkcji, które ułatwiają pracę z Opcjami. Rozważ główny przykład z dokumentów Scali:
Zauważ, jak
map
ifilter
pozwól na łączenie operacji na Opcjach, bez konieczności sprawdzania w każdym punkcie, czy masz,None
czy nie. Następnie na końcu służygetOrElse
do określenia wartości domyślnej. W żadnym momencie nie robisz czegoś „obrzydliwego”, takiego jak sprawdzanie typów. Wszelkie nieuniknione sprawdzanie typu odbywa się wewnętrznie w bibliotece. Haskell ma własny zestaw funkcji analitycznych, nie wspominając o dużym zestawie funkcji, które będą działać na dowolnej monadzie lub funktorze.Inne typy danych algebraicznych mają swoje idiomatyczne sposoby pracy z nimi, a dopasowanie wzorca jest niskie na biegunie totemu w większości przypadków. Podobnie podczas tworzenia własnych typów oczekuje się, że zapewnią podobne funkcje do pracy z nimi.
źródło