Haskell присоединиться к многоуровневой монаде

Я изучаю haskell и пытаюсь максимально использовать аппликативные функторы вместо монад. Он очень аккуратен и прост в составлении. Однако иногда в коде возникают некоторые типы, такие как IO [IO [a]] или IO Maybe IO Maybe a, что доставляет мне большие проблемы. По-видимому, в этих сценариях монады становятся неизбежными.

Я знаю, что есть плоская операция, такая как join:: m (m a) -> m a для одноуровневых монад. Есть ли что-нибудь подобное для многоуровневых монад? Что-нибудь в монадных трансформерах?

Большое спасибо!


person ccaapton    schedule 22.11.2014    source источник


Ответы (2)


Если вы заметили, что m (n _) является преобразователем монады, вы можете определить эту операцию. Это, безусловно, тот случай, когда мы замечаем, что IO (Maybe a) совпадает с MaybeT IO a: тогда мы просто используем join MaybeT. Мы можем сделать это в немалой степени благодаря тому, что Maybe и IO «слоятся» особенно красиво.

С другой стороны, не все «составленные» монады являются преобразователями монад. В частности, IO [a] на самом деле не один. Настоящий преобразователь ListT, который мы хотели бы иметь и который join выглядит

newtype ListT m a = ListT { runListT :: m (Maybe (a, ListT m a)) }

Это тот случай, когда мы можем превратить IO [a] в ListT IO a и наоборот, но не эти операции инвертируют друг друга. Действительно, IO [a] не является монадой в своем собственном праве и не может быть joined.

person J. Abrahamson    schedule 22.11.2014
comment
Короткий ответ заключается в том, что newtype ListT m a = ListT (m [a]) следует шаблону других типов, которые успешно являются монадами, но не соблюдает сами правила для общего m. Я был немного неточен — такая ListT m является монадой именно тогда, когда [] и m коммутируют, например. m [a] изоморфен [m a]. Это отмечено в документах . На Вики также есть эта страница. - person J. Abrahamson; 22.11.2014
comment
С более оперативной точки зрения проблема заключается в том, что m [a] отличается от ListT, который я дал в своем ответе, объединяя все m-эффекты вверху. Это означает, что эффекты списка и m-эффекты работают пакетно, а не переплетаются, что необходимо для соблюдения законов преобразования монад в целом. - person J. Abrahamson; 22.11.2014
comment
Если вы посмотрите на мое описание реализации ListT-правильного выполнения, вы увидите, что Функция stream позволяет нам преобразовать ListT m a в m [a]. Примечательно, что эта операция демонстрирует проблему объединения всех m-эффектов. Кроме того, как я уже заметил, это сбивает стеки, поскольку это означает, что вы не можете на самом деле передавать список лениво — все m-эффекты применяются одновременно. (Сейчас в свете утра я не уверен, почему я назвал его stream, на самом деле) - person J. Abrahamson; 22.11.2014
comment
Я чувствую, что это похоже на случай с zipList, но не может доказать/опровергнуть законы монад. Для IO [ IO [a] ] в настоящее время я использую fmap sequence, чтобы преобразовать их в IO IO [[a]], затем делаю join и fmap join. Он станет IO [a], но я думаю, что это довольно уродливо. - person ccaapton; 22.11.2014
comment
Этот механизм полностью разделяет эффекты IO и []. Это работает до тех пор, пока интересующие вас конкретные эффекты имеют этот IO [a] ~ [IO a], например. эффекты коммутируют. Это может иметь место для некоторых IO эффектов, но вы заметите, что ваше потребление списка результатов и эффекты, возникающие в реальном мире, станут десинхронизированными. Это наиболее явно плохо в случае ленивого ввода-вывода, который либо вынужден быть строгим (за счет соединений и последовательностей на IO), либо недетерминированным из-за того, что использование ресурсов (например, дескрипторы файлов) не коммутирует с использованием списка . - person J. Abrahamson; 22.11.2014
comment
Как насчет ConT[a]? Является ли оно монадическим и коммутативным? Если да, то я могу ограничить все эффекты IO некоторыми IoConT и получить трансформаторы бесплатно. - person ccaapton; 23.11.2014
comment
Поскольку Cont является матерью всех монад, я предполагаю, что, поскольку существует любая некоммутирующая монада, Cont некоммутативна. - person J. Abrahamson; 23.11.2014

Монады вообще не коммутируют, но вы можете предоставить все частные случаи, которые вам нужны:

{-# LANGUAGE MultiParamTypeClasses #-}

import Control.Monad

class (Monad m, Monad n) => Swappable m n where
    swap :: m (n a) -> n (m a)

instance Swappable [] IO where
    swap = sequence

instance Swappable Maybe IO where
    swap  Nothing  = return Nothing
    swap (Just mx) = fmap return mx

cut :: Swappable m n => m (n (m a)) -> n (m a)
cut = liftM join . swap

squash :: Swappable m n => n (m (n (m a))) -> n (m a)
squash = (>>= cut)

Пример:

x :: IO [IO [()]]
x = return $ map (\s -> putStrLn s >> return [()]) ["ab","c"]

y :: IO (Maybe (IO (Maybe ())))
y = return $ Just $ putStrLn "z" >> return (Just ())

main = squash x >> squash y

отпечатки

ab
c
z

ИЗМЕНИТЬ

Вы можете избежать определения нового класса типов, предоставив экземпляр Traversable (есть экземпляры для Maybe и [] в Data.Traversable):

import Data.Traversable as T
import Control.Monad

cut :: (Traversable t, Monad t, Monad m) => t (m (t a)) -> m (t a)
cut = liftM join . T.sequence

squash :: (Traversable t, Monad t, Monad m) => m (t (m (t a))) -> m (t a)
squash = (>>= cut)
person user3237465    schedule 22.11.2014
comment
Большое спасибо! Но я чувствую, что это больше похоже на еще одну реализацию специального трансформатора... - person ccaapton; 23.11.2014