Haskell Monad Transformer Stack and Type Signatures

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

Стек объединяет несколько трансформаторов 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" by Step ", но это просто использует одну функцию состояния aux и одно StateT)?

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

Edit
Здесь компиляционный код, включающий предложения JFT и Edward:

{-# 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 работала, но думаю, что я знаю, куда идти отсюда. Спасибо, ребята.

Ответы

Ответ 1

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

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 [Address] Identity   поэтому позвольте просто использовать State [Address] -}

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)

{-   Пусть отслаивается, что стек от внешнего (lefmost в декларации)   до самого внутреннего был Identity в вашей первоначальной декларации.   Обратите внимание, что 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 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 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 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

Ответ 2

В общем, вы обнаружите, что код становится намного яснее, используя одно 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