W rzeczywistości jest to zwykły konstruktor danych, który jest zdefiniowany w Prelude , która jest standardową biblioteką importowaną automatycznie do każdego modułu.
Co może jest strukturalnie
Definicja wygląda mniej więcej tak:
data Maybe a = Just a
| Nothing
Ta deklaracja definiuje typ, Maybe a
który jest sparametryzowany przez zmienną typu a
, co oznacza po prostu, że można go używać z dowolnym typem zamiast a
.
Konstruowanie i niszczenie
Typ ma dwa konstruktory Just a
i Nothing
. Gdy typ ma wiele konstruktorów, oznacza to, że wartość typu musi zostać skonstruowana za pomocą tylko jednego z możliwych konstruktorów. W przypadku tego typu wartość została utworzona za pośrednictwem Just
lub Nothing
, nie ma innych (bezbłędnych) możliwości.
Ponieważ Nothing
nie ma typu parametru, gdy jest używany jako konstruktor, nazywa stałą wartość, która jest członkiem typu Maybe a
dla wszystkich typów a
. Ale Just
konstruktor ma parametr typu, co oznacza, że używany jako konstruktor zachowuje się jak funkcja od typu a
do Maybe a
, tj. Ma typa -> Maybe a
Zatem konstruktory typu budują wartość tego typu; drugą stroną jest sytuacja, w której chciałbyś użyć tej wartości, i to jest moment, w którym pojawia się dopasowywanie wzorców. W przeciwieństwie do funkcji konstruktorów można używać w wyrażeniach wiążących wzorce i jest to sposób, w jaki można przeprowadzić analizę przypadków wartości należących do typów z więcej niż jednym konstruktorem.
Aby użyć Maybe a
wartości w dopasowaniu do wzorca, musisz podać wzorzec dla każdego konstruktora, na przykład:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
W tym przypadku wyrażenie, pierwszy wzorzec pasowałby, gdyby wartość była Nothing
, a drugi byłby dopasowany, gdyby wartość została skonstruowana za pomocą Just
. Jeśli druga jest zgodna, wiąże również nazwę val
z parametrem, który został przekazany do Just
konstruktora, gdy została skonstruowana wartość, do której pasuje.
Co może oznacza
Może już wiesz, jak to działa; wartości nie mają żadnej magii Maybe
, to po prostu zwykły algebraiczny typ danych Haskella (ADT). Ale jest używany dość często, ponieważ skutecznie „podnosi” lub rozszerza typ, taki jak Integer
z twojego przykładu, do nowego kontekstu, w którym ma dodatkową wartość ( Nothing
), która reprezentuje brak wartości! System typu następnie wymaga sprawdzenia tej dodatkowej wartości, zanim będzie pozwalają uzyskać u Integer
, że może tam być. Zapobiega to znacznej liczbie błędów.
Obecnie wiele języków obsługuje tego rodzaju wartości „bez wartości” za pośrednictwem odwołań NULL. Tony Hoare, wybitny informatyk (wynalazł Quicksort i jest laureatem nagrody Turinga), przyznaje się do tego jako „miliardowy błąd” . Typ Może nie jest jedynym sposobem, aby to naprawić, ale okazał się skutecznym sposobem na zrobienie tego.
Może jako funktor
Pomysł przekształcenia jednego typu na inny, tak aby operacje na starym typie można było również przekształcić, aby działały na nowym typie, jest koncepcją wywoływaną przez klasę typu Haskell Functor
, która Maybe a
ma przydatne wystąpienie.
Functor
udostępnia metodę wywoływaną fmap
, która mapuje funkcje, których zakres obejmuje wartości od typu podstawowego (na przykład Integer
) do funkcji, których zakres obejmuje wartości z typu zniesionego (na przykład Maybe Integer
). Funkcja przekształcona w fmap
celu pracy z Maybe
wartością działa w ten sposób:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
Więc jeśli masz Maybe Integer
wartość m_x
i Int -> Int
funkcję f
, możesz fmap f m_x
zastosować funkcję f
bezpośrednio do funkcji Maybe Integer
bez martwienia się, czy rzeczywiście ma wartość, czy nie. W rzeczywistości możesz zastosować cały łańcuch podniesionych Integer -> Integer
funkcji do Maybe Integer
wartości i musisz się martwić tylko o jawne sprawdzenie Nothing
raz, kiedy skończysz.
Może jako monada
Nie jestem pewien, jak dobrze znasz pojęcie a Monad
, ale przynajmniej używałeś go IO a
wcześniej, a podpis typu IO a
wygląda niezwykle podobnie do Maybe a
. Chociaż IO
jest wyjątkowy, ponieważ nie ujawnia swoich konstruktorów przed tobą i dlatego może być "uruchamiany" tylko przez system wykonawczy Haskell, nadal jest również Functor
dodatkiem do bycia Monad
. W rzeczywistości istnieje ważny sens, w którym a Monad
jest po prostu specjalnym rodzajem Functor
z kilkoma dodatkowymi funkcjami, ale to nie jest miejsce, aby się tym zająć.
W każdym razie, monady lubią IO
odwzorowywać typy na nowe typy, które reprezentują „obliczenia, które dają w wyniku wartości” i możesz podnieść funkcje do Monad
typów za pomocą bardzo fmap
podobnej funkcji zwanej, liftM
która zamienia zwykłą funkcję w „obliczenie, którego wynikiem jest wartość uzyskana przez oszacowanie funkcjonować."
Prawdopodobnie zgadłeś (jeśli przeczytałeś tak daleko), że Maybe
jest to również plik Monad
. Reprezentuje „obliczenia, które mogą nie zwrócić wartości”. Podobnie jak w fmap
przykładzie, pozwala to wykonać całą masę obliczeń bez konieczności jawnego sprawdzania błędów po każdym kroku. I faktycznie, sposób, w jaki Monad
instancja jest skonstruowana, obliczanie Maybe
wartości zatrzymuje się, gdy tylko Nothing
zostanie napotkane, więc jest to trochę jak natychmiastowe przerwanie lub bezwartościowy zwrot w środku obliczenia.
Mogłeś napisać może
Jak powiedziałem wcześniej, nie ma nic nieodłącznego od Maybe
typu, który jest wbudowany w składnię języka lub system wykonawczy. Jeśli Haskell nie zapewniłby go domyślnie, możesz sam zapewnić całą jego funkcjonalność! W rzeczywistości i tak możesz napisać go ponownie, pod różnymi nazwami i uzyskać tę samą funkcjonalność.
Mamy nadzieję, że rozumiesz teraz Maybe
typ i jego konstruktorów, ale jeśli nadal jest coś niejasnego, daj mi znać!
Maybe
tam, gdzie inne języki używająnull
lubnil
(z paskudnymNullPointerException
czającym się za każdym rogiem). Teraz inne języki również zaczynają używać tej konstrukcji: Scala asOption
, a nawet Java 8 będzie miała tenOptional
typ.