Chciałbym zaproponować bardziej systematyczne podejście do odpowiedzi na to pytanie, a także pokazać przykłady, w których nie stosuje się żadnych specjalnych sztuczek, takich jak „najniższe” wartości, nieskończone typy danych lub cokolwiek podobnego.
Kiedy konstruktory typów nie mają instancji klas typów?
Zasadniczo istnieją dwa powody, dla których konstruktor typów może nie mieć instancji określonej klasy typu:
- Nie można zaimplementować sygnatur typu wymaganych metod z klasy typu.
- Może implementować podpisy typu, ale nie może spełniać wymaganych praw.
Przykłady pierwszego rodzaju są łatwiejsze niż przykłady drugiego rodzaju, ponieważ w przypadku pierwszego rodzaju wystarczy sprawdzić, czy można zaimplementować funkcję z danym typem podpisu, natomiast w przypadku drugiego rodzaju musimy udowodnić, że nie ma implementacji mógłby ewentualnie spełniać prawa.
Konkretne przykłady
Jest to kontrafunkcjonariusz, a nie funktor, w odniesieniu do parametru type a
, ponieważ a
w pozycji przeciwstawnej. Niemożliwe jest wdrożenie funkcji z podpisem typu (a -> b) -> F z a -> F z b
.
Konstruktor typów, który nie jest zgodnym z prawem funktorem, mimo że fmap
można zaimplementować podpis typu :
data Q a = Q(a -> Int, a)
fmap :: (a -> b) -> Q a -> Q b
fmap f (Q(g, x)) = Q(\_ -> g x, f x) -- this fails the functor laws!
Ciekawy aspekt tego przykładu jest to, że można wdrożyć fmap
od odpowiedniego typu, chociaż F
nie może być ewentualnie funktor ponieważ wykorzystuje a
w kontrawariantny pozycji. Ta implementacja fmap
pokazana powyżej jest myląca - mimo że ma prawidłową sygnaturę typu (uważam, że jest to jedyna możliwa implementacja tego typu sygnatury), prawa funktora nie są spełnione. Na przykład fmap id
≠ id
, ponieważ let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
jest 123
, ale let (Q(f,_)) = id (Q(read,"123")) in f "456"
jest 456
.
W rzeczywistości F
jest tylko profesorem - nie jest ani funktorem, ani contrafunctor.
Zgodny z prawem funktor, który nie ma zastosowania, ponieważ pure
nie można zaimplementować podpisu typu : weź monadę Writer (a, w)
i usuń ograniczenie, które w
powinno być monoidem. Wówczas niemożliwe jest skonstruowanie wartości typu (a, w)
na podstawie a
.
Funktora że nie jest aplikacyjnych , ponieważ podpis typu <*>
nie mogą być realizowane: data F a = Either (Int -> a) (String -> a)
.
Funktor, który nie jest zgodny z prawem, mimo że można zastosować metody klasy typu:
data P a = P ((a -> Int) -> Maybe a)
Konstruktor typów P
jest funktorem, ponieważ używa a
tylko w pozycjach kowariantnych.
instance Functor P where
fmap :: (a -> b) -> P a -> P b
fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))
Jedyną możliwą implementacją podpisu typu <*>
jest funkcja, która zawsze zwraca Nothing
:
(<*>) :: P (a -> b) -> P a -> P b
(P pfab) <*> (P pa) = \_ -> Nothing -- fails the laws!
Ale ta implementacja nie spełnia prawa tożsamości dla funktorów aplikacyjnych.
- Funktor, który
Applicative
nie jest,Monad
ponieważ bind
nie można zaimplementować podpisu typu
Nie znam takich przykładów!
- Funktor,
Applicative
ale nie jest tak,Monad
ponieważ prawa nie mogą być spełnione, nawet jeśli bind
można zaimplementować podpis typu .
Ten przykład wywołał sporo dyskusji, więc można śmiało powiedzieć, że udowodnienie poprawności tego przykładu nie jest łatwe. Ale kilka osób zweryfikowało to niezależnie różnymi metodami. Zobacz Is `data PoE a = Empty | Sparować monadę? do dodatkowej dyskusji.
data B a = Maybe (a, a)
deriving Functor
instance Applicative B where
pure x = Just (x, x)
b1 <*> b2 = case (b1, b2) of
(Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
_ -> Nothing
Dość trudne jest udowodnienie, że nie ma zgodnej z prawem Monad
instancji. Przyczyną nie-monadycznego zachowania jest to, że nie ma naturalnego sposobu implementacji, bind
kiedy funkcja f :: a -> B b
może zwrócić Nothing
lub Just
dla różnych wartości a
.
Być może bardziej zrozumiałe jest rozważenie Maybe (a, a, a)
, które również nie jest monadą, i próba wdrożenia join
tego. Okaże się, że nie ma intuicyjnie rozsądnego sposobu wdrożenia join
.
join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
join Nothing = Nothing
join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
-- etc.
W przypadkach wskazanych przez ???
wydaje się jasne, że nie możemy wytwarzać Just (z1, z2, z3)
w żaden rozsądny i symetryczny sposób z sześciu różnych wartości typu a
. Z pewnością moglibyśmy wybrać dowolny arbitralny podzbiór tych sześciu wartości - na przykład zawsze przyjmujemy pierwszą niepustą Maybe
- ale to nie spełniałoby praw monady. Powrót nie Nothing
będzie również zgodny z prawem.
- Drzewiasta struktura danych, która nie jest monadą, mimo że ma asocjatywność,
bind
ale nie spełnia praw tożsamości.
Zwykła monada drzewiasta (lub „drzewo z gałęziami w kształcie funktora”) jest zdefiniowana jako
data Tr f a = Leaf a | Branch (f (Tr f a))
Jest to wolna monada nad funktorem f
. Kształt danych jest drzewem, w którym każdy punkt rozgałęzienia jest „funktorem” poddrzewa. Standardowe drzewo binarne można uzyskać za pomocą type f a = (a, a)
.
Jeśli zmodyfikujemy tę strukturę danych, tworząc również liście w kształcie funktora f
, otrzymamy coś, co nazywam „semimonadą” - ma ono bind
spełnienie praw natury i asocjatywności, ale jej pure
metoda nie spełnia jednego z praw tożsamości. „Semimonady to półgrupy w kategorii endofunkcji, w czym problem?” To jest klasa typu Bind
.
Dla uproszczenia definiuję join
metodę zamiast bind
:
data Trs f a = Leaf (f a) | Branch (f (Trs f a))
join :: Trs f (Trs f a) -> Trs f a
join (Leaf ftrs) = Branch ftrs
join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)
Szczepienie gałęzi jest standardowe, ale szczepienie liści jest niestandardowe i powoduje Branch
. Nie stanowi to problemu dla prawa o stowarzyszeniu, ale łamie jedno z praw dotyczących tożsamości.
Kiedy typy wielomianowe mają instancje monad?
Żaden z funktorów Maybe (a, a)
i Maybe (a, a, a)
może być udzielona legalnemu Monad
instancji, choć są oczywiście Applicative
.
Te funktory nie mają żadnych sztuczek - żadnych Void
ani bottom
nigdzie, żadnych trudnych lenistw / ścisłości, żadnych nieskończonych struktur i żadnych ograniczeń klasowych. Applicative
Przykład całkowicie standardowy. Funkcje return
i bind
mogą być zaimplementowane dla tych funktorów, ale nie spełniają praw monady. Innymi słowy, te funktory nie są monadami, ponieważ brakuje konkretnej struktury (ale nie jest łatwo zrozumieć, czego dokładnie brakuje). Na przykład niewielka zmiana funktora może przekształcić go w monadę: data Maybe a = Nothing | Just a
jest monadą. Kolejnym podobnym funktorem data P12 a = Either a (a, a)
jest także monada.
Konstrukcje monad wielomianowych
Ogólnie rzecz biorąc, oto niektóre konstrukcje, które produkują zgodne Monad
z prawem typy wielomianowe. We wszystkich tych konstrukcjach M
jest monada:
type M a = Either c (w, a)
gdzie w
jest jakikolwiek monoid
type M a = m (Either c (w, a))
gdzie m
jest dowolna monada i w
dowolna monoid
type M a = (m1 a, m2 a)
gdzie m1
i m2
są dowolne monady
type M a = Either a (m a)
gdzie m
jest jakakolwiek monada
Pierwsza konstrukcja to WriterT w (Either c)
druga konstrukcja WriterT w (EitherT c m)
. Trzecia konstrukcja jest produktem monad pure @M
składającym się z komponentów : jest definiowana jako produkt składający się z pure @m1
i pure @m2
, i join @M
jest definiowany przez pominięcie danych między produktami (np. m1 (m1 a, m2 a)
Jest odwzorowany m1 (m1 a)
przez pominięcie drugiej części krotki):
join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))
Czwarta konstrukcja jest zdefiniowana jako
data M m a = Either a (m a)
instance Monad m => Monad M m where
pure x = Left x
join :: Either (M m a) (m (M m a)) -> M m a
join (Left mma) = mma
join (Right me) = Right $ join @m $ fmap @m squash me where
squash :: M m a -> m a
squash (Left x) = pure @m x
squash (Right ma) = ma
Sprawdziłem, czy wszystkie cztery konstrukcje produkują legalne monady.
I przypuszczać, że nie istnieją żadne inne zbudowania dla wielomianu monad. Na przykład funktor Maybe (Either (a, a) (a, a, a, a))
nie jest uzyskiwany przez żadną z tych konstrukcji, a zatem nie jest monadyczny. Jednak Either (a, a) (a, a, a)
jest monadycznego ponieważ jest izomorficzna z produktem trzech monad a
, a
oraz Maybe a
. Ponadto Either (a,a) (a,a,a,a)
jest monadyczny, ponieważ jest izomorficzny w stosunku do produktu a
i Either a (a, a, a)
.
Cztery powyższe konstrukcje pozwolą nam uzyskać dowolną sumę dowolnej liczby produktów dowolnej liczby a
, na przykład Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
i tak dalej. Wszystkie tego typu konstruktory będą miały (przynajmniej jedną) Monad
instancję.
Oczywiście okaże się, jakie przypadki użycia mogą istnieć dla takich monad. Inną kwestią jest to, że Monad
przypadki wyprowadzone za pomocą konstrukcji 1-4 nie są na ogół wyjątkowe. Na przykład, konstruktorowi typu type F a = Either a (a, a)
można nadać Monad
instancję na dwa sposoby: przez konstrukcję 4 za pomocą monady (a, a)
i przez konstrukcję 3 za pomocą izomorfizmu typu Either a (a, a) = (a, Maybe a)
. Znów znalezienie przypadków użycia dla tych implementacji nie jest od razu oczywiste.
Pozostaje pytanie - biorąc pod uwagę arbitralny wielomianowy typ danych, jak rozpoznać, czy ma on Monad
instancję. Nie wiem, jak udowodnić, że nie ma innych konstrukcji monad wielomianowych. Nie sądzę, aby jak dotąd istniała jakaś teoria odpowiadająca na to pytanie.
* -> *
), dla którego nie ma odpowiedniegofmap
?