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.Większość obecnych odpowiedzi to wysoce techniczne wyjaśnienia, jak
Just
i przyjaciele pracują; Pomyślałem, że mógłbym spróbować swoich sił w wyjaśnieniu, do czego to służy.Wiele języków ma taką wartość,
null
która może być używana zamiast wartości rzeczywistej, przynajmniej w przypadku niektórych typów. To bardzo rozgniewało wiele osób i zostało powszechnie uznane za zły ruch. Mimo to czasami warto mieć wartość taką, jaknull
wskazanie braku rzeczy.Haskell rozwiązuje ten problem, zmuszając Cię do wyraźnego oznaczania miejsc, w których możesz mieć
Nothing
(jego wersję anull
). Zasadniczo, jeśli twoja funkcja normalnie zwróci typFoo
, zamiast tego powinna zwrócić typMaybe Foo
. Jeśli chcesz wskazać, że nie ma wartości, zwróćNothing
. Jeśli chcesz zwrócić wartośćbar
, powinieneś zamiast tego zwrócićJust bar
.Więc w zasadzie, jeśli nie
Nothing
możesz, nie potrzebujeszJust
. Jeśli możeszNothing
, potrzebujeszJust
.Nie ma w tym nic magicznego
Maybe
; jest zbudowany na systemie typów Haskell. Oznacza to, że możesz użyć z nim wszystkich zwykłych sztuczek dopasowywania wzorców Haskella .źródło
Dla danego typu
t
wartośćJust t
jest istniejącą wartością typut
, gdzieNothing
reprezentuje nieosiągnięcie wartości lub przypadek, w którym posiadanie wartości byłoby bez znaczenia.W Twoim przykładzie ujemne saldo nie ma sensu, więc jeśli coś takiego się wydarzy, zostanie zastąpione przez
Nothing
.Na przykład można to wykorzystać do dzielenia, definiując funkcję dzielenia, która przyjmuje
a
andb
, i zwraca,Just a/b
jeślib
jest różna od zera, i wNothing
przeciwnym razie. Jest często używany w ten sposób, jako wygodna alternatywa dla wyjątków lub jak w poprzednim przykładzie, do zastępowania wartości, które nie mają sensu.źródło
Just
, twój kod nie byłby typecheck. PowodemJust
jest zachowanie właściwych typów. Istnieje typ (właściwie monada, ale łatwiej o nim myśleć jako o typie)Maybe t
, który składa się z elementów formyJust t
iNothing
. PonieważNothing
ma typMaybe t
, wyrażenie, które może obliczyć jednąNothing
lub jakąś wartość typu,t
nie jest poprawnie wpisane. Jeśli funkcja zwracaNothing
w niektórych przypadkach, każde wyrażenie używające tej funkcji musi mieć jakiś sposób na sprawdzenie tego (isJust
lub instrukcję case), aby obsłużyć wszystkie możliwe przypadki.Maybe t
to tylko typ. Fakt, że istniejeMonad
instancja dlaMaybe
, nie przekształca go w coś, co nie jest typem.Całkowita funkcja a-> b może znaleźć wartość typu b dla każdej możliwej wartości typu a.
W Haskell nie wszystkie funkcje są totalne. W tym konkretnym przypadku funkcja
lend
nie jest całkowita - nie jest zdefiniowana dla przypadku, gdy saldo jest mniejsze niż rezerwa (chociaż według mojego gustu bardziej sensownym byłoby niedopuszczenie, aby newBalance było mniejsze niż rezerwa - tak jak jest, możesz pożyczyć 101 z saldo 100).Inne projekty, które dotyczą funkcji niecałkowitych:
lend
można napisać, aby zwrócić stare saldo, jeśli warunek pożyczki nie zostanie spełnionySą to niezbędne ograniczenia projektowe w językach, które nie mogą egzekwować wszystkich funkcji (na przykład Agda może, ale prowadzi to do innych komplikacji, takich jak niekompletność turinga).
Problem z zwracaniem specjalnej wartości lub wyrzucaniem wyjątków polega na tym, że wywołujący łatwo omyłkowo pominąć obsługę takiej możliwości.
Problem z dyskretnym odrzucaniem awarii jest również oczywisty - ograniczasz to, co dzwoniący może zrobić z funkcją. Na przykład, jeśli
lend
zwróci stare saldo, dzwoniący nie ma możliwości dowiedzenia się, czy saldo się zmieniło. Może to stanowić problem, ale nie musi, w zależności od zamierzonego celu.Rozwiązanie Haskella zmusza obiekt wywołujący funkcję częściową do radzenia sobie z typem takim jak
Maybe a
lub zEither error a
powodu zwracanego typu funkcji.W ten sposób,
lend
zgodnie z definicją, jest to funkcja, która nie zawsze oblicza nowy bilans - w pewnych okolicznościach nowy bilans nie jest zdefiniowany. Sygnalizujemy tę okoliczność dzwoniącemu, zwracając specjalną wartość Nothing lub opakowując nową równowagę w Just. Dzwoniący ma teraz swobodę wyboru: albo poradzi sobie z niepowodzeniem pożyczki w specjalny sposób, albo zignoruje i skorzysta ze starego salda - na przykładmaybe oldBalance id $ lend amount oldBalance
.źródło
Funkcja
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
musi mieć ten sam typifTrue
iifFalse
.Więc kiedy piszemy
then Nothing
, musimy użyćMaybe a
typu inelse f
źródło
Nothing
iJust newBalance
jawny.Just
.