Стек трансформаторов Haskell Monad и сигнатуры типов
я пытаюсь создать стек трансформаторов монады, и у меня возникли проблемы с получением правильных сигнатур типов для моих функций. (Я все еще довольно новичок в Haskell)
стек объединяет несколько трансформаторов состояния, так как у меня есть несколько состояний, которые мне нужно отслеживать (два из которых могут быть кортежами, но я доберусь до этого через секунду) и 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
Итак, может ли кто-нибудь сказать мне, что происходит, и дать объяснение / показать мне правильные подписи типа, или кто-нибудь знает хорошую ссылку на этот материал (единственное, что помогло до сих пор, это "трансформаторы монады шаг за шагом", но это просто использует одну функцию aux state и один StateT)?
заранее большое спасибо.
редактировать
Вот код компиляции, включающий 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
мне все еще нужно заставить часть монады писателя работать, но думаю, что я знаю, куда идти отсюда. Спасибо, ребята.
2 ответов
было много проблем с вашим фрагментом кода. Я исправил ваш фрагмент, добавив объяснение о том, что было сломано, и добавил несколько советов по стилю, если вам интересно.
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 [адрес] 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)
{- Давайте лупить, что стек из крайних (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 " Для этого давайте получим 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 для консультация если требуется
check ":t incLineNum and :t curLineNum"
-}
incLineNum = Pass1 . lift $ modify (+1)
curLineNum = Pass1 $ lift get
evalr = do l <- popLine
incLineNum
return l
там это длинный запыхавшийся ответ, но монада и стек монады, как вы видите, сначала бросают вызов. Я исправил код, но я призываю вас играть и проверять типы различных функции, чтобы понять, что происходит, и сравнить с вашим оригиналом. Вывод типа Haskell означает, что обычно аннотации типа излишни (если только не удалять двусмысленность). В общем, тип, который мы бы дали функции, менее общий, что было выводом, поэтому лучше не вводить аннотацию. Аннотация типа-это, безусловно, хороший метод отладки;)
Ура
П. С. реальный мир Хаскелл главе монады трансформатор отлично: http://book.realworldhaskell.org/read/monad-transformers.html
В общем, вы обнаружите, что код заканчивается намного яснее, используя один StateT с большей составной структурой для всех битов состояния, которые вам нужны. Одна из причин заключается в том, что, когда вы придумываете кусок состояния, вы забыли, что всегда можете вырастить структуру одним полем, и вы можете использовать сахар записи для записи обновлений одного поля или обратиться к чему-то вроде fclabels или пакетов доступа к данным для управления состоянием.
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