Стеки и типы трансформаторов в Haskell Monad - PullRequest
14 голосов
/ 18 января 2010

Я пытаюсь создать стек монадных преобразователей и у меня проблемы с получением правильных сигнатур типов для моих функций. (Я все еще довольно новичок в Хаскеле)

Стек объединяет несколько преобразователей StateT, так как у меня есть несколько состояний, которые мне нужно отслеживать (два из которых могут быть переопределены, но я вернусь к этому через секунду) и WriterT для ведения журнала.

Вот что у меня есть:

module Pass1 where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
import Types

data Msg = Error String
         | Warning String

type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a


runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)


--popLine :: (MonadState s m) => m (Maybe s)
--popLine :: (Monad m) => StateT [Line] m (Maybe Line)
popLine :: (MonadState s m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing


incLineNum :: (Num s, MonadState s m) => m ()
incLineNum = do
               ln <- get
               put $ ln + 1

curLineNum :: (MonadState s m) => m s
curLineNum = do
               ln <- get
               return ln

evalr = do l <- popLine
           --incLineNum
           return l

Я бы хотел, чтобы popLine связывался с состоянием [Line] и функциями xLineNum, влияющими на состояние Int. evalr - это вычисление, которое будет передано runPass1.

Всякий раз, когда я загружаю код, я сталкиваюсь с ошибками, которые обычно имеют следующий вид:

Pass1.hs:23:14:
    No instance for (MonadState [t] m)
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix: add an instance declaration for (MonadState [t] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }


Pass1.hs:22:0:
    Couldn't match expected type `s' against inferred type `[Line]'
      `s' is a rigid type variable bound by                        
          the type signature for `popLine' at Pass1.hs:21:23        
    When using functional dependencies to combine                  
      MonadState [Line] m,                                         
        arising from a use of `get' at Pass1.hs:23:14-16            
      MonadState s m,                                              
        arising from the type signature for `popLine'              
                     at Pass1.hs:(22,0)-(28,31)                     
    When generalising the type(s) for `popLine'         




Pass1.hs:23:14:
    Could not deduce (MonadState [Line] m)
      from the context (MonadState s m)   
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix:                                    
      add (MonadState [Line] m) to the context of    
        the type signature for `popLine'             
      or add an instance declaration for (MonadState [Line] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }

Кажется, что ни одна из сигнатур не верна, но popLine - первая функция, поэтому единственная, которая сразу же вызывает ошибку.

Я пытаюсь добавить то, что он предлагает, в сигнатуру типа (например: popLine :: (MonadState [Line] m) => ..., но тогда это приводит к ошибкам, таким образом:

Pass1.hs:21:0:
    Non type-variable argument in the constraint: MonadState [Line] m
    (Use -XFlexibleContexts to permit this)                          
    In the type signature for `popLine':                             
      popLine :: (MonadState [Line] m) => m (Maybe Line)

Кажется, я всегда получаю это сообщение всякий раз, когда пытаюсь сделать что-то, что не является переменной типа. Кажется, ему нравится (MonadState s m) ok и ошибка на чем-то другом, но когда я пробую это с [a] вместо s, это ошибки, подобные вышеописанным. (Первоначально [Line] и Int были объединены в одно состояние, но я получал эту ошибку, поэтому решил попробовать перевести их в отдельные состояния).

GHC 6.10.4, Kubuntu

Итак, кто-нибудь может сказать мне, что происходит, и дать объяснение / показать мне правильные подписи типа, или кто-нибудь знает хорошую ссылку на этот материал (единственное, что помогло до сих пор, это "Monad Transformers Step шаг за шагом ", но это просто использует одну вспомогательную функцию состояния и один StateT)?

Заранее большое спасибо.

Редактировать
Вот код компиляции, включающий предложения JFT и Эдварда:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad)
{-# LANGUAGE MultiParamTypeClasses #-}      -- needed for: MonadState instance
{-# LANGUAGE FlexibleContexts #-}           -- needed for: (MonadState PassState m) => ...

module Pass1 where
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import Types

type Lines     = [Line]
type Addresses = [Address]
type LineNum   = Int
type Messages  = [Msg]
data Msg = Error String
         | Warning String

data PassState = PassState { passLineNum :: LineNum
                           , passLines :: Lines
                           , passAddresses :: Addresses
                           }

newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a
                        }
                        deriving (Functor,Monad)

instance MonadState PassState Pass1 where
        get   = Pass1 . lift $ get
        put s = Pass1 . lift $ put s



runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState)
runPass1 state = flip runState state .
                 runWriterT          .
                 unPass1


curLineNum :: (MonadState PassState m) => m LineNum
curLineNum = do
               state <- get
               return $ passLineNum state


nextLine :: (MonadState PassState m) => m (Maybe Line)
nextLine = do
             state <- get
             let c = passLineNum state
             let l = passLines state
             case l of
               x:xs -> do
                         put state { passLines = xs, passLineNum = (c+1) }
                         return $ Just x
               _ -> return Nothing



evalr :: Pass1 (Maybe Line,LineNum)
evalr = do
          l <- nextLine
          c <- curLineNum
          --tell $ Warning "hello"
          return (l,c)

Я объединил incLineNum и popLine в nextLine Мне все еще нужно заставить работать монаду Writer, но, думаю, я знаю, куда идти дальше. Спасибо, ребята.

Ответы [ 2 ]

39 голосов
/ 18 января 2010

Было много проблем с вашим фрагментом кода. Я исправил ваш фрагмент, добавив объяснение того, что было сломано, и добавил несколько советов по стилю, если вам все равно.

module Pass1_JFT where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

{- замена типов импорта простыми определениями -}

--import Types
type Line       = String
type Address    = String
type LineNumber = Int

{- Не часть вашего вопроса, но мои 2 цента здесь ... Скажите, что вы хотите изменить коллекцию для своих штатов, если вы этого не сделаете используйте псевдоним типа, на который вам придется охотиться везде, где вы его использовали. Вместо этого просто изменить эти определения, если требуется -}

type Lines     = [Line]
type Addresses = [Address]
type Messages  = [Msg]


data Msg = Error String
         | Warning String

{- Что это за Int в StateT Int? Назовите его легче читать, причина о и изменить. Декларативный FTW давайте использовать LineNumber вместо -}

--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a

{- Давайте использовать «настоящий» тип, чтобы экземпляры могли быть получены. Поскольку Pass1 не является передачей монады, т.е. не определен как Pass1 m a, нет смысла использовать StateT для самого глубокого StateT, то есть StateT [Address] Identity так что давайте просто использовать государство [адрес] -}

newtype Pass1 a = Pass1 {
    unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a
                        }
                        deriving (Functor,Monad)

--runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)

{- Давайте очистим этот стек от самого внешнего (самого левого в объявлении) до самой глубины была идентичность в вашей первоначальной декларации. Обратите внимание, что runWriterT НЕ принимает начальное состояние ... Первый параметр для runStateT (и runState) не является начальным состоянием но монада ... так что давайте перевернемся! -}

runPass1' :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1' addrs instrs msgs = flip runState addrs   .
                              flip runStateT instrs .
                              flip runStateT 1      .
                              runWriterT            . -- then get process the WriterT (the second outermost)
                              unPass1                 -- let's peel the outside Pass1

{- теперь эта последняя функция не делает то, что вы хотите, так как вы хотите предоставить начальный журнал для добавления в WriterT. Так как это монадный трансформатор, мы сделаем здесь несколько трюков -}

-- I keep the runStateT convention for the order of the arguments: Monad then state
runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w)
runWriterT' writer log = do
    (result,log') <- runWriterT writer
    -- let's use the monoid generic append in case you change container...
    return (result,log `mappend` log')

runPass1 :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1 addrs instrs msgs = flip runState addrs   .
                             flip runStateT instrs .
                             flip runStateT 1      .
                             flip runWriterT' msgs . -- then get process the WriterT (the second outermost)
                             unPass1                 -- let's peel the outside Pass1

{- Вы собираетесь вызывать popLine непосредственно из стека Pass1? Если это так, вам нужно «научить» Pass1 быть «MonadState Lines» Для этого давайте выведем Pass1 (вот почему мы объявили его с newtype!) -}

instance MonadState Lines Pass1 where
    -- we need to dig inside the stack and "lift" the proper get
    get   = Pass1 . lift . lift $ get
    put s = Pass1 . lift . lift $ put s

{- Лучше держать вещь универсальной, но теперь мы могли бы написать: popLine :: Pass1 (возможно, строка) -}

popLine :: (MonadState Lines m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing

{- Хорошо, теперь я получаю Int => LineNumber .... мы могли бы сделать Pass1 и экземпляр MonadState LineNumber, но LineNumber не должно быть перепутано, поэтому вместо этого я бы закодировал incLine напрямую и предоставит экземпляр MonadReader для консульства, если потребуется

check ":t incLineNum and :t curLineNum"

-}

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

Это длинный ответ, но монада и стек монад, как вы видите, поначалу бросают вызов. Я исправил код, но призываю вас поиграть и проверить типы различных функций, чтобы понять, что происходит, и сравнить с вашим оригиналом. Вывод типа в Haskell означает, что обычно аннотации типов являются излишними (если только не устранить неоднозначность). В общем, тип, который мы дали бы функции, является менее общим, чем был бы выведен, поэтому лучше не вводить аннотирование. Тип аннотации, безусловно, хорошая техника отладки;)

Приветствия

P.S. Глава по реальному миру на Haskell о Monad Transformer превосходна: http://book.realworldhaskell.org/read/monad-transformers.html

12 голосов
/ 18 января 2010

В общем, вы обнаружите, что код получается гораздо яснее, если использовать один StateT с большей составной структурой для всех необходимых вам битов состояния. Одна из веских причин заключается в том, что, когда вы сталкиваетесь с частью состояния, которую вы забыли, вы всегда можете увеличить структуру на одно поле, и вы можете использовать сахар записи, чтобы записывать обновления одного поля или обращаться к чему-то вроде fclabels или data-accessor пакеты для манипулирования состоянием.

data PassState = PassState { passLine :: Int, passLines :: [Line] }

popLine :: MonadState PassState m => m (Maybe Line).   
popLine = do
   state <- get
   case passLines state of
      x:xs -> do 
         put state { passLines = xs }
         return (Just x)
      _ -> return Nothing
...