2012-03-13 21 views
13

Dadas dos mónadas, Monad m y Monad n, me gustaría transformar m (n a) en n (m a). Pero parece que no hay una forma genérica porque (>>=) y return tratan con un solo tipo de mónada, y aunque (>>=) permite extraer contenido de una mónada, debe empacarlos al mismo tipo de mónada para que pueda ser un valor de resultado.Reempazar mónadas: ¿una forma genérica?

Sin embargo, si configuramos m en un tipo fijo, el trabajo es fácil. Tome Maybe como ejemplo:

reorder :: (Monad n) => Maybe (n a) -> n (Maybe a) 
reorder Nothing = return Nothing 
reorder (Just x) = do 
    x' <- x 
    return $ Just x' 

o una lista:

reorder :: (Monad n) => [n a] -> n [a] 
reorder [] = return [] 
reorder (x:xs) = do 
    x' <- x 
    xs' <- reorder xs 
    return (x':xs') 

No es difícil de ver, tenemos un patrón aquí. Para ser más claro, escribirlo de una manera Applicative, y no es más que aplicar el constructor de datos para cada elemento:

reorder (Just x) = Just <$> x 
reorder (x:xs) = (:) <$> x <*> (reorder xs) 

Mi pregunta es: hace una clase de tipos de Haskell ya existen para describir este tipo de operaciones, o tengo tengo que inventar la rueda yo mismo?

Tuve una breve búsqueda en la documentación de GHC, y no encontré nada útil para este tema.

Respuesta

14

Data.Traversable responde a lo que busca:

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a) 

GHC incluso proporciona soporte para los casos que se derivan automáticamente:

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-} 
import Data.Foldable 
import Data.Traversable 

data List a = Nil | Cons a (List a) 
    deriving(Functor, Foldable, Traversable) 
+0

Esto, por supuesto, solo funciona cuando el constructor de tipo externo es un 'Traversable' (no todos los 'Monad's son). La ventaja es que el interno solo tiene que ser 'Aplicativo'. –

5

Una búsqueda rápida en Hoogle para (Monad m, Monad n) => m (n a) -> n (m a) me mostró que hay varias funciones que (aproximadamente) cumpla con la firma que está buscando:

  1. Data.Traversable.sequence :: (Traversable t, Monad m) => t (m a) -> m (t a);
  2. Data.Traversable.sequenceA :: Applicative f => t (f a) -> f (t a);
  3. Contro.Monad.sequence :: Monad m => [m a] -> m [a] (también exportado por Prelude).

Tanto [a] y Maybe a son ejemplos de transitable, por lo que sus funciones son sólo de reordenar las aplicaciones de Data.Traversable.sequence. Se podría escribir, en el ejemplo:

ghci> (Data.Traversable.sequence $ Just (return 1)) :: IO (Maybe Int) 
Just 1 
it :: Maybe Int 

ghci> (Data.Traversable.sequence $ Just ([1])) :: [Maybe Int] 
[Just 1] 
it :: [Maybe Int] 

ghci> (Data.Traversable.sequence $ [Just 1]) :: Maybe [Int] 
Just [1] 
it :: Maybe [Int] 

Tenga en cuenta sin embargo, que la declaración de clase específica es class (Functor t, Foldable t) => Traversable t, y si nos fijamos también en los tipos de las otras dos funciones, que no parece ser lo que estás buscando podría hacerse de forma genérica para todas las mónadas m y n sin restricciones/condiciones previas.

1

No todas las Monads pueden viajar de esa manera. paquete de Edward Kmett distributive proporciona una clase de tipos Distributive de constructores de tipo que es similar a lo que usted desea (simplificado): se proporcionan

class Functor g => Distributive g where 
    distribute :: Functor f => f (g a) -> g (f a) 
    collect  :: Functor f => (a -> g b) -> f a -> g (f b) 

definiciones predeterminadas para distribute y collect, escrita en términos de la otra.Encontré este paquete por searching hayoo for the desired type signature.

4

No se puede hacer en general: un buen ejemplo de una mónada que no puede hacer esto es la mónada de lector (o función). Que requeriría la siguiente función para ser definible:

impossible :: (r -> IO a) -> IO (r -> a) 

No es fácil de probar que una función no puede ser implementado. Pero, intuitivamente, el problema es que cualquier cosa que IO tenga que hacerse en el valor devuelto tiene que hacerse antes de que sepamos cuál es el parámetro r. Por lo tanto, impossible readFile tendría que generar una función pura FilePath -> String antes de saber qué archivos abrir. Claramente, al menos, impossible no puede hacer lo que le gustaría.

Cuestiones relacionadas