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é ain 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 fmappuò 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 fmapdel tipo corretto, anche se Fnon può possibilmente essere un funtore perché utilizza ain una posizione controvariante. Quindi questa implementazione di quella fmapmostrata 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 purenon può essere implementata: prendi la monade Writer (a, w)e rimuovi il vincolo che wdovrebbe 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 asolo 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 è
Applicativema nonMonad perché la firma del tipo di bindnon può essere implementata.
Non conosco esempi del genere!
- Un funtore che è
Applicativema non unMonad perché le leggi non possono essere soddisfatte anche se la firma del tipo di bindpuò 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 Monadun'istanza legittima . La ragione del comportamento non monadico è che non esiste un modo naturale di implementare bindquando una funzione f :: a -> B bpotrebbe restituire Nothingo Justper valori diversi di a.
È forse più chiaro considerare Maybe (a, a, a), che anche non è una monade, e provare a implementare joinper 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 Nothinginoltre 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 bindche soddisfa le leggi di naturalità e associatività, ma il suo puremetodo 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 joinmetodo 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 Monadun'istanza legittima , sebbene lo siano ovviamente Applicative.
Questi funtori non hanno trucchi: niente Voido da bottomnessuna parte, nessuna pigrizia / rigore complicato, nessuna struttura infinita e nessun tipo di vincoli di classe. L' Applicativeistanza è completamente standard. Le funzioni returne bindpossono 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 Monads legali da tipi polinomiali. In tutte queste costruzioni, Mè una monade:
type M a = Either c (w, a)dov'è wun 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 m1e m2sono tutte le monadi
type M a = Either a (m a)dov'è muna 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 @m1e 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 ae 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) Monadistanza.
Resta da vedere, ovviamente, quali casi d'uso potrebbero esistere per tali monadi. Un altro problema è che le Monadistanze 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 Monadun'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 Monadun'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?