Использовать монаду списка внутри классов типов преобразователей монад?

Моя цель - создать функцию, которая использует монаду списка внутри стека ReaderT WriterT или стека RWS. В более общем плане, как мне использовать монаду списка внутри классов типов mtl, таких как MonadReader, MonadWriter?

Почему я пытаюсь это сделать? Эта проблема является упражнением в Beginning Haskell. Он просит меня «использовать функциональность как MonadReader, так и MonadWriter, обертывающую монаду базового списка. Чтобы проверить, является ли функция общей, используйте две разные монады для [проверки] запрошенной функциональности: ReaderT r (WriterT w []) a и RWST r w s m a» Итак, книга подразумевает, что это возможно.

Я не могу понять, как «сказать» компилятору использовать монаду списка. Если я использую ask >>= lift или ask >>= lift . lift, я могу заставить работать либо двухуровневый стек (RWST []), либо трехуровневый стек (ReaderT WriterT []), но не оба сразу.

В центре моего вопроса:

pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

Кроме того, я хотел бы знать, как набрать функцию. Моя лучшая попытка до сих пор выглядит примерно как pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m () Я знаю, что это неправильно, монада списка, вероятно, отсутствует. Кроме того, я думаю, что MonadPlus может быть полезен в сигнатуре типа, но я не совсем уверен.

Эта строка: do (s0, e0) <- ask >>= lift вызывает у меня проблемы. Я безуспешно пробовал 0, 1 и 2 подъема. Я хотел бы ask для [(Int, Int)], а затем использовать монаду списка для обработки только (Int, Int) (и позволить монаде списка испробовать все возможности для меня).

В рамках упражнения мне нужно иметь возможность вызывать pathImplicitStack' с помощью обеих этих функций (или очень похожих функций):

pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
  where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()

pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
  where exec = execRWST (pathImplicitStack' start end) edges ()

Это связано с моим предыдущим вопросом: Как использовать монаду списка внутри ReaderT?

Весь файл для удобного тестирования:

{-# LANGUAGE FlexibleContexts #-}

module Foo where

import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.RWS

graph1 :: [(Int, Int)]
graph1 = [(2013,501),(2013,1004),(501,2558),(1004,2558)]


pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
  where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()

pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
  where exec = execRWST (pathImplicitStack' start end) edges ()

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

изменить

Основываясь на отзывах Джона Л., я попробовал

pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t []), MonadTrans t) => Int -> Int -> t [] ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

но, как он указал, его можно использовать только с одним преобразователем монад для обертывания монады списка, то есть RSWT, и его нельзя использовать с ReaderT WriterT. Так что это не то решение, которое я ищу.


person Daniel K    schedule 12.06.2014    source источник
comment
MonadPlus - это в основном класс типа монады списка. Например: cons a as = return a `mplus` as и nil = mzero.   -  person Gabriel Gonzalez    schedule 13.06.2014


Ответы (2)


Итак, основная проблема с выполнением этого в рамках требований вопроса заключается в том, что нет функции библиотеки MTL для подъема из монады списка, которая может быть произвольным уровнем вниз. Однако вы можете немного "обмануть": экземпляр MonadPlus для объединенного Monad унаследован от базовой монады списка независимо от глубины, и вы можете использовать его для генерации необходимого действия:

  do  (s0, e0) <- ask >>= msum . map return

Также возникает ошибка в сигнатуре типа, которую необходимо изменить на:

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()

РЕДАКТИРОВАТЬ: На самом деле, если подумать, это на самом деле не обман. Он просто использует MonadPlus API для связывания альтернативных действий вместо прямого использования базовой монады списка.

person Ørjan Johansen    schedule 13.06.2014
comment
Это сработало, спасибо! Я тоже не считаю MonadPlus обманом. Я уже упоминал об этом в книге, поэтому мне кажется, что это честная игра. - person Daniel K; 14.06.2014

Я думаю, что сложность здесь в том, что вы смешиваете слои монады. Смотря на

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]

Эта функция возвращает список m () вычислений, однако ask >>= lift (и ваш предыдущий вопрос) предполагают, что List является базовой монадой, на которую вы складываете дополнительные трансформаторы. Если вы хотите использовать List в качестве базовой монады, вам нужно изменить тип pathImplicitStack'

pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t [])) => Int -> Int -> t [] ()

Но даже это не является достаточно общим, потому что это позволяет добавить только один трансформатор поверх списка. Вы можете использовать библиотеку операторов типов для объединения двух преобразователей монад в один преобразователь, но для этого это кажется немного сложным.

Вот один из вариантов: использовать Identity в качестве базовой монады и выполнять операции со списком вне этого стека монад. (предупреждение, весь код не протестирован, он может даже не компилироваться)

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      edges <- filter ((== start) . fst) <$> ask
      let m (s0,e0) = tell [s0] >> pathImplicitStack' e0 end
      mapM_ m edges

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

pathImplicitStack'2 :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m) => Int -> Int -> ListT m ()
pathImplicitStack'2 start end | start == end = tell [end]
pathImplicitStack'2 start end =
  do  (s0, e0) <- ask
      guard $ s0 == start
      tell [start]
      pathImplicitStack'2 e0 end
-- if you have MonadReader/MonadWriter instances for ListT in scope, I think this will
-- work.  But if they aren't available, you will need to use `lift ask` and 
-- `lift (tell ...)`

Я почти наверняка выбрал бы первый подход.

person John L    schedule 13.06.2014
comment
К сожалению, упражнение, представленное мне в книге (см. Мою правку), хочет, чтобы моя функция могла использоваться как стеком ReaderT WriterT, так и RSWT, и оно хочет, чтобы они «оборачивали монаду базового списка». Итак, для целей упражнения первый подход недостаточно общий. В книге не представлены библиотеки операторов типов, так что я почти уверен, что это не «ответ». И если list не является внутренней монадой, я не получу правильный ответ [[Int]] от Writer. - person Daniel K; 13.06.2014
comment
@DanielK, вы можете просто заменить lift на liftBase (из hackage.haskell.org/package/transformers-base) в вашей функции, и это сработает. Но я тоже не думаю, что liftBase является частью ожидаемого ответа. - person John L; 13.06.2014