LiftM può differire da liftA?
-
06-07-2019 - |
Domanda
Secondo la Typeclassopedia (tra le altre fonti), Applicative
appartiene logicamente tra Monad
e Pointed
(e quindi Functor
) nella gerarchia di classi di tipi, quindi idealmente avremmo qualcosa del genere se il preludio di Haskell fosse scritto oggi:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor f => Pointed f where
pure :: a -> f a
class Pointed f => Applicative f where
(<*>) :: f (a -> b) -> f a -> f b
class Applicative m => Monad m where
-- either the traditional bind operation
(>>=) :: (m a) -> (a -> m b) -> m b
-- or the join operation, which together with fmap is enough
join :: m (m a) -> m a
-- or both with mutual default definitions
f >>= x = join ((fmap f) x)
join x = x >>= id
-- with return replaced by the inherited pure
-- ignoring fail for the purposes of discussion
(Dove quelle definizioni predefinite sono state riscritte da me dalla spiegazione su Wikipedia , gli errori sono miei, ma se ci sono errori è almeno in linea di principio possibile.)
Dato che le librerie sono attualmente definite, abbiamo:
liftA :: (Applicative f) => (a -> b) -> f a -> f b
liftM :: (Monad m) => (a -> b) -> m a -> m b
e
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
ap :: (Monad m) => m (a -> b) -> m a -> m b
Nota la somiglianza tra questi tipi all'interno di ciascuna coppia.
La mia domanda è: sono liftM
(distinti da liftA
) e ap
(distinti da <*>
), semplicemente il risultato della realtà storica che <=> non è stato progettato con <= > e <=> in mente? Oppure sono in qualche altro modo comportamentale (potenzialmente, per alcune <=> definizioni legali) distinti dalle versioni che richiedono solo un <=> contesto?
Se sono distinti, potresti fornire un semplice insieme di definizioni (obbedendo alle leggi richieste di <=>, <=>, <=> e <=> definizioni descritte nella Typeclassopedia e altrove ma non applicate dal tipo di sistema) per cui <=> e <=> si comportano diversamente?
In alternativa, se non sono distinti, potresti provare la loro equivalenza usando le stesse leggi delle premesse?
Soluzione
liftA
, liftM
, fmap
e .
dovrebbero avere tutti la stessa funzione, e devono esserlo se soddisfano la legge del funzione :
fmap id = id
Tuttavia, questo non è verificato da Haskell.
Ora per Applicativo. È possibile che ap
e <*>
siano distinti per alcuni funzioni semplicemente perché potrebbe esserci più di un'implementazione che soddisfa i tipi e le leggi. Ad esempio, Elenco ha più di un'istanza Applicative
possibile. Potresti dichiarare un applicativo come segue:
instance Applicative [] where
(f:fs) <*> (x:xs) = f x : fs <*> xs
_ <*> _ = []
pure = repeat
La funzione liftM2 id
verrebbe comunque definita come Monad
, che è l'istanza []
fornita gratuitamente con ogni newtype ZipList a = ZipList [a]
. Ma qui hai un esempio di un costruttore di tipi con più di un'istanza ZipList
, che soddisfano entrambe le leggi. Ma se le tue monadi e i tuoi agenti applicativi non sono d'accordo, è considerato una buona forma avere tipi diversi per loro. Ad esempio, la <=> istanza sopra non è d'accordo con la monade per <=>, quindi dovresti davvero dire <=> e quindi creare la nuova istanza per <=> anziché <=>.
Altri suggerimenti
Loro possono differire, ma non dovrebbero .
Possono differire perché possono avere implementazioni diverse: una è definita in instance Applicative
mentre l'altra è definita in instance Monad
. Ma se effettivamente differiscono, direi che il programmatore che ha scritto quelle istanze ha scritto un codice fuorviante.
Hai ragione: le funzioni esistono come fanno per ragioni storiche. Le persone hanno idee forti su come avrebbero dovuto essere le cose.