LiftM peut-il différer de liftA?
-
06-07-2019 - |
Question
Selon Typeclassopedia (entre autres sources), Applicative
appartient logiquement entre Monad
et Pointed
(et donc Functor
) dans la hiérarchie des classes de types; nous aurions donc idéalement quelque chose comme ceci si le prélude Haskell était écrit aujourd'hui:
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
(Où ces définitions par défaut ont été retapées par moi à partir de la explication sur Wikipedia , les erreurs étant les miennes, mais s’il existe des erreurs, c’est au moins en principe possible.)
Comme les bibliothèques sont actuellement définies, nous avons:
liftA :: (Applicative f) => (a -> b) -> f a -> f b
liftM :: (Monad m) => (a -> b) -> m a -> m b
et:
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
ap :: (Monad m) => m (a -> b) -> m a -> m b
Notez la similitude entre ces types dans chaque paire.
Ma question est la suivante: sont liftM
(par opposition à liftA
) et ap
(par opposition à <*>
), tout simplement le résultat de la réalité historique selon laquelle <=> n'a pas été conçu avec <= > et <=> à l'esprit? Ou sont-ils d'une autre manière comportementale (potentiellement, pour certaines <=> définitions légales) distinctes des versions qui nécessitent uniquement un <=> contexte?
Si elles sont distinctes, pouvez-vous fournir un ensemble simple de définitions (respectant les lois requises des définitions <=>, <=>, <=> et <=> décrites dans Typeclassopedia et ailleurs, mais non appliquées par le type de système) pour lequel <=> et <=> se comportent différemment?
Sinon, si elles ne sont pas distinctes, pourriez-vous prouver leur équivalence en utilisant les mêmes lois que les locaux?
La solution
liftA
, liftM
, fmap
et .
devraient avoir la même fonction et ils doivent être s'ils satisfont à la loi du foncteur :
fmap id = id
Cependant, cela n'est pas vérifié par Haskell.
Maintenant pour l'application. Il est possible que ap
et <*>
soient distincts pour certains foncteurs simplement parce qu'il peut y avoir plusieurs implémentations qui répondent aux types et aux lois. Par exemple, List a plusieurs instances possibles Applicative
. Vous pouvez déclarer un applicatif comme suit:
instance Applicative [] where
(f:fs) <*> (x:xs) = f x : fs <*> xs
_ <*> _ = []
pure = repeat
La fonction liftM2 id
serait toujours définie comme Monad
, qui est l'instance []
fournie gratuitement avec chaque newtype ZipList a = ZipList [a]
. Mais ici, vous avez un exemple de constructeur de type ayant plus d'une instance ZipList
, les deux satisfaisant les lois. Mais si vos monades et vos foncteurs applicatifs ne sont pas d’accord, on considère qu’il est judicieux d’en avoir différents types. Par exemple, l'instance <=> ci-dessus ne correspond pas à la monade de <=>. Vous devez donc réellement dire <=>, puis créer la nouvelle instance pour <=> au lieu de <=>.
Autres conseils
Ils peuvent différer, mais ils ne devraient pas .
Ils peuvent différer car ils peuvent avoir différentes implémentations: l’une est définie dans un instance Applicative
, tandis que l’autre est définie dans un instance Monad
. Mais s’ils diffèrent, je dirais que le programmeur qui a écrit ces instances a écrit un code trompeur.
Vous avez raison: les fonctions existent comme elles le sont pour des raisons historiques. Les gens ont des idées bien arrêtées sur la façon dont les choses auraient dû se passer.