haskell - ¿Puede liftM diferir de liftA?
theory typeclass (2)
Pueden diferir, pero no deberían .
Pueden diferir porque pueden tener diferentes implementaciones: una se define en un instance Applicative
mientras que la otra se define en una instance Monad
. Pero si realmente difieren, entonces diría que el programador que escribió esas instancias escribió un código engañoso.
Tienes razón: las funciones existen como lo hacen por razones históricas. Las personas tienen ideas sólidas sobre cómo deberían haber sido las cosas.
De acuerdo con Typeclassopedia (entre otras fuentes), Applicative
lógicamente pertenece entre Monad
y Pointed
(y por lo tanto, Functor
) en la jerarquía de clases de tipos, por lo que idealmente tendríamos algo como esto si el preludio de Haskell se escribiera hoy:
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
(Donde esas definiciones predeterminadas fueron reescritas por mí a partir de la explicación en Wikipedia , los errores son míos, pero si hay errores, al menos en principio es posible).
Como las bibliotecas están actualmente definidas, tenemos:
liftA :: (Applicative f) => (a -> b) -> f a -> f b
liftM :: (Monad m) => (a -> b) -> m a -> m b
y:
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
ap :: (Monad m) => m (a -> b) -> m a -> m b
Tenga en cuenta la similitud entre estos tipos dentro de cada par.
Mi pregunta es: ¿son liftM
(a diferencia de liftA
) y ap
(a diferencia de <*>
), simplemente un resultado de la realidad histórica de que Monad
no fue diseñada teniendo en cuenta los Pointed
y Applicative
? ¿O son de alguna otra manera conductual (potencialmente, para algunas definiciones legales de la Monad
) distintas de las versiones que solo requieren un contexto Applicative
?
Si son distintos, ¿podría proporcionar un conjunto simple de definiciones (obedeciendo las leyes requeridas de las definiciones de Monad
, Applicative
, Pointed
y Functor
descritas en Typeclassopedia y en otros lugares pero no aplicadas por el sistema de tipos) para las cuales liftA
y liftM
comportan de manera diferente?
Alternativamente, si no son distintos, ¿podría probar su equivalencia utilizando esas mismas leyes como premisas?
liftA
, liftM
, fmap
, y .
todas deben ser la misma función, y deben ser si cumplen la ley del functor:
fmap id = id
Sin embargo, esto no está comprobado por Haskell.
Ahora para aplicativo. Es posible que ap
y <*>
sean distintos para algunos funtores simplemente porque podría haber más de una implementación que satisfaga los tipos y las leyes. Por ejemplo, List tiene más de una instancia de Applicative
posible. Podrías declarar un aplicativo de la siguiente manera:
instance Applicative [] where
(f:fs) <*> (x:xs) = f x : fs <*> xs
_ <*> _ = []
pure = repeat
La función ap
todavía se definiría como liftM2 id
, que es la instancia de Applicative
que viene gratis con cada Monad
. Pero aquí tiene un ejemplo de un constructor de tipo que tiene más de una instancia de Applyative, las cuales cumplen con las leyes. Pero si sus mónadas y sus funtores aplicativos no están de acuerdo, se considera una buena forma tener diferentes tipos para ellos. Por ejemplo, la instancia de Applicative
anterior no está de acuerdo con la mónada para []
, por lo que realmente debería decir newtype ZipList a = ZipList [a]
y luego crear la nueva instancia para ZipList
lugar de []
.