2011-09-10 12 views
6

He estado tratando de "aprenderme un Haskell" a través del libro en línea LYAH.una generalización simple de la clase de tipo Applicative (Functor); coincidencia de patrones en constructores

El autor describe el comportamiento de los Funcionadores del tipo Aplicativo como si tuvieran la capacidad de extraer una función de un funtor y asignarla a un segundo funtor; esto es a través del *> función < declarado para la clase de tipo aplicativo:

class (Functor f) => Applicative f where 
    pure :: a -> f a 
    (<*>) :: f (a -> b) -> f a -> f b 

Como un simple ejemplo, el tipo Tal vez es una instancia de Aplicativo bajo la siguiente aplicación:

instance Applicative Maybe where 
    pure = Just 
    Nothing <*> _ = Nothing 
    (Just f) <*> something = fmap f something 

Un ejemplo de el comportamiento mencionado anteriormente:

ghci> Just (*2) <*> Just 10   -- evaluates to Just 20 

por lo que el *> operador < "extrae" el (* 2) función del primer operando y el mapa s en el segundo operando.

Ahora, en los tipos Applicative, ambos operandos de < *> son del mismo tipo, así que pensé como ejercicio, ¿por qué no intentar implementar una generalización de este comportamiento, donde los dos operandos son Functors de diferentes tipos, por lo que pude evaluar algo como esto:

Just (2*) <*:*> [1,2,3,4] -- should evaluate to [2,4,6,8] 

Así que esto es lo que ocurrió:

import Control.Applicative 

class (Applicative f, Functor g) => DApplicative f g where 
    pure1 :: a -> f a 
    pure1 = pure 
    (<*:*>) :: f (a -> b) -> g a -> g b  -- referred below as (1) 

instance DApplicative Maybe [] where -- an "instance pair" of this class 
    (Just func) <*:*> g = fmap func g 

main = do putStrLn(show x) 
    where x = Just (2*) <*:*> [1,2,3,4] -- it works, x equals [2,4,6,8] 

Ahora, a pesar de lo anterior funciona, me pregunto si podemos hacerlo mejor; ¿Es posible dar una implementación predeterminada para < *: *> que se puede aplicar a una variedad de pares f & g, en la declaración para DApplicative f g en sí? Y esto me lleva a la siguiente pregunta: ¿Hay alguna forma de emparejar patrones en constructores en diferentes tipos de datos?

espero que mis preguntas hacen algún sentido y no estoy simplemente arrojando sin sentido (si lo estoy, por favor, no ser demasiado duro, sólo soy un principiante hasta FP camino más allá de su hora de dormir ...)

Respuesta

5

Esto tiene sentido, pero termina siendo poco útil en su forma actual. El problema es exactamente lo que ha notado: no hay forma de proporcionar un valor predeterminado que haga cosas sensatas con diferentes tipos, o convertir generalmente de f a g. Entonces, tendrías una explosión cuadrática en el número de instancias que necesitarías escribir.

No terminó la instancia DApplicative. He aquí una aplicación completa para Maybe y []:

instance DApplicative Maybe [] where -- an "instance pair" of this class 
    (Just func) <*:*> g = fmap func g 
    Nothing  <*:*> g = [] 

Esto combina las conductas de ambos Maybe y [], porque con Just lo hace lo que se espera, pero con Nothing no devuelve nada, una lista vacía.

Entonces, en lugar de escribir DApplicative que toma dos tipos diferentes, ¿qué pasaría si tuviera una forma de combinar dos aplicativos f y g en un solo tipo? Si generaliza esta acción, podría usar un estándar Applicative con el nuevo tipo.

Esto podría hacerse con la formulación estándar de aplicativos como

liftAp :: f (g (a -> b)) -> f (g a) -> f (g b) 
liftAp l r = (<*>) <$> l <*> r 

sino que vamos a cambiar Maybe:

import Control.Applicative 

newtype MaybeT f a = MaybeT { runMaybeT :: f (Maybe a) } 

instance (Functor f) => Functor (MaybeT f) where 
    fmap f (MaybeT m) = MaybeT ((fmap . fmap) f m) 

instance (Applicative f) => Applicative (MaybeT f) where 
    pure a = MaybeT (pure (pure a)) 
    (MaybeT f) <*> (MaybeT m) = MaybeT ((<*>) <$> f <*> m) 

Ahora sólo tiene una manera de convertir algo en el aplicativo interno, f , en el aplicativo combinado MaybeT f:

lift :: (Functor f) => f a -> MaybeT f a 
lift = MaybeT . fmap Just 

Esto parece una gran repetición, pero ghc puede derivar automáticamente casi todo.

Ahora usted puede utilizar fácilmente las funciones combinadas:

*Main Control.Applicative> runMaybeT $ pure (*2) <*> lift [1,2,3,4] 
[Just 2,Just 4,Just 6,Just 8] 

*Main Control.Applicative> runMaybeT $ MaybeT (pure Nothing) <*> lift [1,2,3,4] 
[Nothing,Nothing,Nothing,Nothing] 

Este comportamiento en nada puede ser sorprendente, pero si se piensa en una lista como la representación de indeterminismo es probable que pueda ver cómo podría ser útil. Si desea el comportamiento dual de devolver Just [a] o Nothing, solo necesita una Lista transformada ListT Maybe a.

Esto no es exactamente lo mismo que la instancia que escribí para DApplicative. La razón es por los tipos. DApplicative convierte un f en un g. Eso solo es posible cuando conoces los f y g específicos. Para generalizarlo, el resultado debe combinar los comportamientos de f y g como lo hace esta implementación.

Todo esto también funciona con Mónadas. Los tipos transformados tales como MaybeT son proporcionados por bibliotecas de transformadores monad como mtl.

+0

gracias por la respuesta completa ... Todavía no he revisado tu código a fondo, pero entiendo la idea general. – Aky

1

La instancia de DApplicativeMaybe no es completa: lo que debería ocurrir si el primer argumento de <*:*> es Nothing?

La elección de qué hacer para cada combinación no está clara. Para dos listas, <*> generaría todas las combinaciones: [(+2),(+10)] <*> [3,4] da [5,6,13,14]. Para dos ZipList s tiene un comportamiento tipo zip: (ZipList [(+2),(+10)]) <*> (ZipList [3,4]) da [5,14]. Entonces, debe elegir uno de los dos comportamientos posibles para un DApplicative de una lista y un ZipList, no hay una versión "correcta".

+0

@ Daniel Wagner: ¡Gracias! Corregido – Landei

+0

esa es una buena observación – Aky

Cuestiones relacionadas