2009-10-28 22 views
29

Según the Typeclassopedia (entre otras fuentes), Applicative pertenece lógicamente entre Monad y Pointed (y por lo tanto Functor) en la jerarquía de clases de tipo, por lo que lo ideal sería tener algo como esto si el preludio Haskell fueron escritas hoy:¿Puede liftM diferir de liftA?

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 

(. Cuando esas definiciones predeterminadas fueron re-introducidas por mí desde el explanation at Wikipedia, los errores de ser mi propio, pero si hay errores es al menos en principio es posible)

a medida que las bibliotecas se definen actualmente, 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 

notar la similitud entre estos tipos dentro de cada par.

Mi pregunta es: ¿liftM (a diferencia de liftA) y ap (a diferencia de <*>), simplemente el resultado de la realidad histórica que Monad no fue diseñado con Pointed y Applicative en mente? ¿O están de alguna otra manera conductual (potencialmente, para algunas definiciones legales Monad) distintas de las versiones que solo requieren un contexto Applicative?

Si son distintos, ¿Podría dar un simple conjunto de definiciones (obedeciendo las leyes necesarias de Monad, Applicative, Pointed y Functor definiciones descritas en el Typeclassopedia y en otros lugares, pero no impuestas por el sistema de tipos) para los que liftA y liftM se comportan de manera diferente?

Alternativamente, si no son distintas, ¿podría probar su equivalencia utilizando esas mismas leyes como premisa?

Respuesta

22

liftA, liftM, fmap y .debe todo sea la misma función, y debe ser si cumplen la ley funtor:

fmap id = id 

Sin embargo, esto no se verifica por Haskell.

Ahora para el 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 posible instancia de Applicative. Se podría 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 el Applicative instancia que viene gratis con cada Monad. Pero aquí tiene un ejemplo de un constructor de tipo que tiene más de una instancia Applicative, ambas cumplen las leyes. Pero si sus mónadas y sus funtores aplicativos no están de acuerdo, se considera buena forma tener diferentes tipos para ellos.Por ejemplo, la instancia 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 hacer la nueva instancia de ZipList en lugar de [].

+2

OK, eso es cierto, hay más de una instancia Aplicativo y más de una instancia Mónada para algunos tipos. Pero, según las definiciones anteriores, la implementación pure/return debería compartirse entre la instancia Applicative y la instancia de Monad. ¿Esto significa que, para cumplir las leyes, que <*> y ap tendrían que ser lo mismo? Parece que tu ejemplo se basa en hacer una instancia Aplicativo para [] = pura con la repetición, pero sigue utilizando el preludio ejemplo Mónada para [] con el regreso = ([])? –

+1

puedo estar perdiendo algo, pero no es cierto que una función del tipo 'forall a, b. (a -> b) -> F a -> F b' se garantiza que es 'fmap'. Por ejemplo, considere 'notmap f xs = zipWith ($) (repeat (const. F $ head xs)) xs'. –

+1

@Apocalisp: teniendo en cuenta sólo el tipo de fmap, todavía puede tener miembros exóticos. La condición lateral fmap id = id es suficiente para forzar el problema, y ​​creo que el cuantificador extra que pones en 'f' es infiel. ;) Doug, mientras que la definición de Functor es única dadas las leyes, las leyes Aplicativas son lo suficientemente flexibles como para permitir múltiples definiciones conformes. Sin embargo, por convención, la Mónada y la Aplicativa para un tipo dado deben ser compatibles. –

8

Ellos pueden difieren, pero ellos no deberían.

Pueden ser diferentes, ya que pueden tener diferentes implementaciones: uno se define en una instance Applicative mientras que el otro 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.

tiene usted razón: existen las funciones que lo hacen por razones históricas. La gente tiene ideas fuertes sobre cómo deberían haber sido las cosas.

Cuestiones relacionadas