Переупаковка монад - любой общий способ?
Учитывая две монады, 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 и не нашел ничего полезного для этой темы.
Ответы
Ответ 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 для требуемой сигнатуры типа.