Vorrei proporre un approccio più sistematico per rispondere a questa domanda e anche mostrare esempi che non utilizzano trucchi speciali come i valori "inferiori" o tipi di dati infiniti o qualcosa del genere.
Quando i costruttori di tipi non riescono ad avere istanze di classi di tipo?
In generale, ci sono due ragioni per cui un costruttore di tipi potrebbe non riuscire ad avere un'istanza di una determinata classe di tipo:
- Impossibile implementare le firme del tipo dei metodi richiesti dalla classe del tipo.
- Può implementare le firme del tipo ma non può soddisfare le leggi richieste.
Gli esempi del primo tipo sono più facili di quelli del secondo tipo perché per il primo tipo, dobbiamo solo verificare se si può implementare una funzione con una data firma del tipo, mentre per il secondo tipo, dobbiamo dimostrare che nessuna implementazione potrebbe eventualmente soddisfare le leggi.
Esempi specifici
Questo è un controfuntore, non un funtore, rispetto al parametro di tipo a
, perché a
in posizione controvariante. È impossibile implementare una funzione con la firma del tipo (a -> b) -> F z a -> F z b
.
Un costruttore di tipi che non è un funtore legale anche se la firma del tipo di fmap
può essere implementata:
data Q a = Q(a -> Int, a)
fmap :: (a -> b) -> Q a -> Q b
fmap f (Q(g, x)) = Q(\_ -> g x, f x)
L'aspetto curioso di questo esempio è che si può implementare fmap
del tipo corretto, anche se F
non può possibilmente essere un funtore perché utilizza a
in una posizione controvariante. Quindi questa implementazione di quella fmap
mostrata sopra è fuorviante - anche se ha la firma di tipo corretta (credo che questa sia l'unica implementazione possibile di quella firma di tipo), le leggi del funtore non sono soddisfatte. Ad esempio, fmap id
≠ id
, perché let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
è 123
, ma let (Q(f,_)) = id (Q(read,"123")) in f "456"
è 456
.
In realtà, F
è solo un profunctor, - non è né un funtore né un controfuntore.
Un funtore legale che non è applicativo perché la firma del tipo di pure
non può essere implementata: prendi la monade Writer (a, w)
e rimuovi il vincolo che w
dovrebbe essere un monoide. È quindi impossibile costruire un valore di tipo (a, w)
da a
.
Un funtore che non è applicativa perché la firma di tipo <*>
non può essere attuata: data F a = Either (Int -> a) (String -> a)
.
Un funtore che non è applicativo legale anche se i metodi della classe di tipo possono essere implementati:
data P a = P ((a -> Int) -> Maybe a)
Il costruttore del tipo P
è un funtore perché utilizza a
solo in posizioni covarianti.
instance Functor P where
fmap :: (a -> b) -> P a -> P b
fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))
L'unica possibile implementazione della firma del tipo di <*>
è una funzione che restituisce sempre Nothing
:
(<*>) :: P (a -> b) -> P a -> P b
(P pfab) <*> (P pa) = \_ -> Nothing
Ma questa implementazione non soddisfa la legge sull'identità per i funtori applicativi.
- Un funtore che è
Applicative
ma nonMonad
perché la firma del tipo di bind
non può essere implementata.
Non conosco esempi del genere!
- Un funtore che è
Applicative
ma non unMonad
perché le leggi non possono essere soddisfatte anche se la firma del tipo di bind
può essere implementata.
Questo esempio ha generato un bel po 'di discussioni, quindi è sicuro dire che dimostrare che questo esempio è corretto non è facile. Ma molte persone lo hanno verificato in modo indipendente con metodi diversi. Vedere È `data PoE a = Empty | Associare aa` una monade? per ulteriori discussioni.
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
È piuttosto complicato dimostrare che non esiste Monad
un'istanza legittima . La ragione del comportamento non monadico è che non esiste un modo naturale di implementare bind
quando una funzione f :: a -> B b
potrebbe restituire Nothing
o Just
per valori diversi di a
.
È forse più chiaro considerare Maybe (a, a, a)
, che anche non è una monade, e provare a implementare join
per questo. Si scoprirà che non esiste un modo di implementazione intuitivamente ragionevole 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)) = ???
Nei casi indicati da ???
, sembra chiaro che non possiamo produrre Just (z1, z2, z3)
in modo ragionevole e simmetrico su sei diversi valori di tipo a
. Potremmo certamente scegliere qualche sottoinsieme arbitrario di questi sei valori, - per esempio, prendere sempre il primo non vuoto Maybe
- ma questo non soddisferebbe le leggi della monade. Il ritorno Nothing
inoltre non soddisferà le leggi.
- Una struttura dati ad albero che non è una monade anche se ha associatività per
bind
- ma non soddisfa le leggi sull'identità.
La solita monade ad albero (o "un albero con rami a forma di funtore") è definita come
data Tr f a = Leaf a | Branch (f (Tr f a))
Questa è una monade libera sul funtore f
. La forma dei dati è un albero in cui ogni punto di diramazione è un "funtore" di sottoalberi. L'albero binario standard sarebbe ottenuto con type f a = (a, a)
.
Se modifichiamo questa struttura di dati facendo anche le foglie a forma di funtore f
, otteniamo quello che chiamo un "semimonad" - ha bind
che soddisfa le leggi di naturalità e associatività, ma il suo pure
metodo fallisce una delle leggi di identità. "I semimonad sono semigruppi nella categoria degli endofunctors, qual è il problema?" Questa è la classe del tipo Bind
.
Per semplicità definisco il join
metodo invece di 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)
L'innesto del ramo è standard, ma l'innesto fogliare non è standard e produce a Branch
. Questo non è un problema per la legge sull'associatività, ma infrange una delle leggi sull'identità.
Quando i tipi polinomiali hanno istanze di monade?
Nessuno dei funtori Maybe (a, a)
e Maybe (a, a, a)
può ricevere Monad
un'istanza legittima , sebbene lo siano ovviamente Applicative
.
Questi funtori non hanno trucchi: niente Void
o da bottom
nessuna parte, nessuna pigrizia / rigore complicato, nessuna struttura infinita e nessun tipo di vincoli di classe. L' Applicative
istanza è completamente standard. Le funzioni return
e bind
possono essere implementate per questi funtori ma non soddisfano le leggi della monade. In altre parole, questi funtori non sono monadi perché manca una struttura specifica (ma non è facile capire cosa manchi esattamente). Ad esempio, un piccolo cambiamento nel funtore può trasformarlo in una monade: data Maybe a = Nothing | Just a
è una monade. Un altro funtore simile data P12 a = Either a (a, a)
è anche una monade.
Costruzioni per monadi polinomiali
In generale, ecco alcune costruzioni che producono Monad
s legali da tipi polinomiali. In tutte queste costruzioni, M
è una monade:
type M a = Either c (w, a)
dov'è w
un monoide
type M a = m (Either c (w, a))
dove m
è qualsiasi monade ed w
è qualsiasi monoide
type M a = (m1 a, m2 a)
dove m1
e m2
sono tutte le monadi
type M a = Either a (m a)
dov'è m
una monade
La prima costruzione è WriterT w (Either c)
, la seconda è WriterT w (EitherT c m)
. La terza costruzione è un prodotto per componente delle monadi: pure @M
è definita come il prodotto per componente di pure @m1
e pure @m2
, ed join @M
è definita omettendo i dati del prodotto incrociato (ad esempio, m1 (m1 a, m2 a)
è mappata m1 (m1 a)
omettendo la seconda parte della tupla):
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))
La quarta costruzione è definita come
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
Ho verificato che tutte e quattro le costruzioni producano monadi legali.
I Ipotizzo che non ci sono altre costruzioni per monadi polinomi. Ad esempio, il funtore Maybe (Either (a, a) (a, a, a, a))
non si ottiene attraverso nessuna di queste costruzioni e quindi non è monadico. Tuttavia, Either (a, a) (a, a, a)
è monade perché è isomorfo al prodotto di tre monadi a
, a
, e Maybe a
. Inoltre, Either (a,a) (a,a,a,a)
è monadico perché è isomorfo al prodotto di a
e Either a (a, a, a)
.
Le quattro costruzioni mostrate sopra ci permetteranno di ottenere qualsiasi somma di qualsiasi numero di prodotti di qualsiasi numero di a
, per esempio, Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
e così via. Tutti questi costruttori di tipi avranno (almeno una) Monad
istanza.
Resta da vedere, ovviamente, quali casi d'uso potrebbero esistere per tali monadi. Un altro problema è che le Monad
istanze derivate tramite le costruzioni 1-4 in generale non sono uniche. Ad esempio, il costruttore del tipo type F a = Either a (a, a)
può ricevere Monad
un'istanza in due modi: dalla costruzione 4 usando la monade (a, a)
e dalla costruzione 3 usando l'isomorfismo del tipo Either a (a, a) = (a, Maybe a)
. Ancora una volta, trovare casi d'uso per queste implementazioni non è immediatamente ovvio.
Rimane una domanda: dato un tipo di dati polinomiale arbitrario, come riconoscere se ha Monad
un'istanza. Non so come dimostrare che non ci siano altre costruzioni per le monadi polinomiali. Non credo che finora esista alcuna teoria per rispondere a questa domanda.
* -> *
) per la quale non esiste alcun adattofmap
?