2010-01-17 3 views
14

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

Стек объединяет несколько трансформаторов 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)?

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

Редактировать
Вот компиляции кода включения 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) 

I комбинированных incLineNum и popLine в nextLine мне еще нужно, чтобы получить часть Writer монады работать, но думаю, что я знаю, где идти отсюда. Спасибо, парни.

ответ

39

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

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 ма, нет смысла, используя StateT для глубокой StateT т.е. StateT [адрес] Удостоверение так давайте просто использовать государство [адрес] -}

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 в декларации) до самого внутреннего - это идентификация в вашем первоначальном объявлении. Обратите внимание, что 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 не должен быть перепутаны с так вместо этого я бы кода Свисающего непосредственно и обеспечат экземпляр MonadReader для consulation если требуются

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. Real World Haskell глава о Монаде Transformer превосходен: http://book.realworldhaskell.org/read/monad-transformers.html

+5

Героизм должен быть вознагражден. +1 –

+0

Удивительный, спасибо ОЧЕНЬ много! Пошаговое объяснение - это то, что мне нужно, спасибо, что нашли время, чтобы сделать это! Ваши предложения по стилю были также оценены. Да, RWH - отличная книга, сейчас у меня есть копия, передо мной. Я думаю, что моя проблема заключается в том, чтобы прочесть ее слишком быстро - это такой красивый язык, я с нетерпением жду его! (кстати, для тех, кто пытается запустить код JFT эти GHC расширения должны быть включены :) {- # LANGUAGE GeneralizedNewtypeDeriving # -} {- # LANGUAGE TypeSynonymInstances # -} {- # LANGUAGE FlexibleContexts # - } {- # LANGUAGE MultiParamTypeClasses # -} – paul

+2

Кроме того, аккуратный трюк, вставляющий ваши комментарии в {- и -}, я должен буду помнить, что при размещении на досках объявлений. Делает копирование кода, чтобы запустить его очень легко. – paul

12

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

Хороший вызов, переводя все переменные состояния в один тип данных, который мне не приходил. (Я все еще привык к Haskell, по какой-то причине я все время получаю туннельное видение, думая об этом.) Я думал о том, чтобы поместить их в кортеж, но хотел избежать беспорядочного кода, который мог бы вызвать. Таким образом, сложены StateT. Но ваше предложение очищает это прямо вверх! Хорошее «будущее», как вы сказали. Благодаря! – paul

+2

Когда биты информации, которую вы отслеживаете, связаны с одной и той же концепцией, это действительно очень хорошая идея (например, состояние синтаксического анализа). С другой стороны, есть случаи, когда вы предпочитаете сохранять ортогональное слежение, следовательно, более многоразовое. Мой DSL на работе отслеживает символы, типы назначения, среду, журнал и многое другое. Все они ортогональны, поэтому у меня есть стек монады, чтобы отделить эти проблемы, и я фактически повторно использовал часть стека в разных областях системы, которым не нужен «полный» стек. – JFT

+0

JFT: Конечно, проблема в том, что вы завершаете среду, в которой вам нужно знать магическое число «лифтов», чтобы добраться до вашего MonadState, чтобы делать правильные вещи, или тратить все свое время на беспорядки с новым типом шума.Подход с комбинированным состоянием может быть расширен за счет того, что вы выбираете класс из полей, которые хотите присутствовать в более чем одной форме состояния, или используя двойной подход «типы данных a la carte» и создавайте что-то вроде (StateT (Линии: *: LineCount)) m, но это слишком сильно напоминает OOHaskell, чтобы я был полностью доволен этим. –

Смежные вопросы