Подтвердить что ты не робот

Переупаковка монад - любой общий способ?

Учитывая две монады, Monad m и Monad n, я хотел бы преобразовать m (n a) в n (m a). Но, как представляется, не существует общего способа, поскольку и (>>=), и return имеет дело только с одним типом монады, и хотя (>>=) позволяет извлекать содержимое из монады, вы должны упаковать их обратно в один и тот же тип монады, чтобы он мог значение результата.

Однако, если мы установим m на фиксированный тип, задание станет легким. Возьмите Maybe в качестве примера:

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

Или список:

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

Не трудно увидеть, у нас есть образец здесь. Чтобы быть более очевидным, напишите его с помощью Applicative, и это не более чем применение конструктора данных к каждому элементу:

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

Мой вопрос: существует ли уже класс haskell для описания таких операций, или я должен сам изобретать колесо?

У меня был короткий поиск в документации GHC и не нашел ничего полезного для этой темы.

4b9b3361

Ответ 1

Data.Traversable предоставляет то, что вы ищете:

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

GHC даже обеспечивает поддержку автоматического вывода экземпляров:

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

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

Ответ 2

Быстрый поиск по hoogle для (Monad m, Monad n) => m (n a) -> n (m a) показал мне, что есть несколько функций, которые (грубо) соответствуют подписи, которую вы ищете:

Оба [a] и Maybe a являются экземплярами обхода, поэтому ваши функции переупорядочения являются просто приложениями Data.Traversable.sequence. Можно написать в примере:

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]

Обратите внимание, что конкретное объявление класса class (Functor t, Foldable t) => Traversable t, и если вы также смотрите на типы двух других функций, похоже, что то, что вы ищете, возможно, было бы сделано общим способом для все монады m и n без ограничений/предварительных условий.

Ответ 3

Это не может быть сделано в целом: хорошим примером монады, которая не может это сделать, является монада-читатель (или функция). Для этого потребуется определить следующую функцию:

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

Непросто доказать, что функция не может быть реализована. Но интуитивно, проблема в том, что все, что IO должно быть сделано в возвращаемом значении, должно быть выполнено, прежде чем мы узнаем, что такое параметр r. Поэтому impossible readFile должен был бы дать чистую функцию FilePath -> String, прежде чем он узнает, какие файлы нужно открыть. Понятно, что, по крайней мере, impossible не может делать то, что вы хотите.

Ответ 4

Не все Монады могут коммутировать таким образом. Edward Kmett distributive пакет предоставляет тип Distributive для конструкторов типов, который похож на то, что вы желаете (упрощенно):

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)

Определения по умолчанию предоставляются для distribute и collect, написанных в терминах друг друга. Я нашел этот пакет поиск hayoo для требуемой сигнатуры типа.