Стек трансформаторов монад Haskell и сигнатуры типов
-
21-09-2019 - |
Вопрос
Я пытаюсь создать стек монадных преобразователей, и у меня возникают проблемы с получением правильных сигнатур типов для моих функций.(Я все еще новичок в 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
Итак, кто-нибудь может рассказать мне, что происходит, и дать объяснение / показать мне правильные сигнатуры типов, или кто-нибудь знает хорошую ссылку на этот материал (единственное, что помогло до сих пор, это "Monad Transformers Step by 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 работать, но думаю, я знаю, что делать дальше.Спасибо, ребята.
Решение
Было много проблем с вашим фрагментом кода.Я исправил ваш фрагмент, добавив объяснение того, что было сломано, и добавил несколько советов по стилю, если вам не все равно.
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, т.е.Идентификатор состояния [Адреса] итак, давайте просто использовать состояние [Адрес] -}
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)
{- Давайте очистим этот стек от самого внешнего (самого левого в объявлении) до самого внутреннего идентификатора was в вашем исходном объявлении.Обратите внимание, что 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
{- Лучше оставить все как есть, но сейчас мы могли бы написать:Поплин ::Проход 1 (Возможно, строка) -}
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 не следует путать с 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
Там это многословный ответ, но monad и monad stack, как вы видите, поначалу являются сложными.Я исправил код, но я рекомендую вам поиграть и изучить типы различных функций, чтобы понять, что происходит, и сравнить с вашим оригиналом.Вывод типа Haskell означает, что обычно аннотации типов являются излишними (если только не для устранения двусмысленности).В общем, тип, который мы бы присвоили функции, менее общий, чем тот, который был выведен, поэтому лучше не вводить annotate .Однако аннотация типа, безусловно, является хорошим методом отладки ;)
Ваше здоровье
P.S.Глава о реальном мире Haskell, посвященная Monad Transformer, превосходна:http://book.realworldhaskell.org/read/monad-transformers.html
Другие советы
В целом вы обнаружите, что код становится намного понятнее, если использовать один 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