Я хочу создать структуру графа, используя IntMap узлов и уникальных ключей. Эта тема хорошо освещена здесь и здесь. Я понимаю, как работает монада состояния, в основном оборачивая функцию состояния -> (val, state) в новый тип, чтобы мы могли создать для нее экземпляр монады. Я довольно много читал на эту тему. Я до сих пор не могу понять, как я могу получить уникальные (или просто дополнительные) значения во время выполнения моей программы. Достаточно легко получить серию последовательных идентификаторов, но как только я «runState» для выхода из монады, кажется, что я вернулся к тому, с чего начал, с необходимости отслеживать текущий идентификатор. Я чувствую, что застрял в монаде. Другой вариант, который я рассматривал, заключался в том, чтобы сохранить всю IntMap и текущий «следующий» идентификатор в качестве состояния, но это кажется очень «императивным» и экстремальным. Этот вопрос очень похож, но не получил много ответов (или, может быть, я просто что-то упустил очевидный). Каков идиоматический способ использования монады состояния для получения уникального идентификатора во время выполнения программы? Спасибо.
Застрял в государственной монаде
Ответы (1)
Давайте представим, что нам нужно IO
-ифицировать State
монаду. Как бы это выглядело? Наша чистая монада State
— это просто новый тип:
s -> (a, s)
Что ж, версия IO
может иметь небольшие побочные эффекты, прежде чем возвращать окончательные значения, которые будут выглядеть так:
s -> IO (a, s)
Этот шаблон настолько распространен, что у него есть имя, а именно StateT
:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Имя имеет T
в конце, потому что это монада T
transformer. Мы называем m
«базовой монадой», а StateT s m
«преобразованной» монадой.
StateT s m
— это Monad
, только если m
— это Monad
:
instance (Monad m) => Monad (StateT s m) where {- great exercise -}
Однако в дополнение к этому все преобразователи монад реализуют класс MonadTrans
, определенный следующим образом:
class MonadTrans t where
lift :: (Monad m) => m a -> t m a
instance MonadTrans (StateT s) where {- great exercise -}
Если t
равно StateT s
, то тип lift
специализируется на:
lift :: m a -> StateT s m a
Другими словами, это позволяет нам «поднять» действие в базовой монаде, чтобы оно стало действием в преобразованной монаде.
Итак, для вашей конкретной проблемы вам нужна монада StateT (IntMap k v) IO
, которая расширяет IO
дополнительными State
. Затем вы можете написать всю свою программу в этой монаде:
main = flip runStateT (initialState :: IntMap k v) $ do
m <- get -- retrieve the map
lift $ print m -- lift an IO action
(k, v) <- lift readLn
put (insert k v m)
Обратите внимание, что я все еще использую get
и put
. Это потому, что пакет transformers
реализует все концепции, которые я описал, и обобщает сигнатуры get
и put
следующим образом:
get :: (Monad m) => StateT s m s
put :: (Monad m) => s -> StateT s m ()
Это означает, что они автоматически работают в пределах StateT
. transformers
затем просто определяет State
как:
type State s = StateT s Identity
Это означает, что вы можете использовать get
и put
как для State
, так и для StateT
.
Чтобы узнать больше о преобразователях монад, я настоятельно рекомендую Преобразователи монад - шаг за шагом а>.
runState
. Единственная ситуация, когда вы не можете просто вытолкнутьrunState
наружу, это когда вам нужен доступ к затененной внешней монаде (скажем, IO). В этот момент вам понадобятся трансформаторы, как предлагает Габриэль. - person Peter Wortmann   schedule 25.01.2013