Застрял в государственной монаде

Я хочу создать структуру графа, используя IntMap узлов и уникальных ключей. Эта тема хорошо освещена здесь и здесь. Я понимаю, как работает монада состояния, в основном оборачивая функцию состояния -> (val, state) в новый тип, чтобы мы могли создать для нее экземпляр монады. Я довольно много читал на эту тему. Я до сих пор не могу понять, как я могу получить уникальные (или просто дополнительные) значения во время выполнения моей программы. Достаточно легко получить серию последовательных идентификаторов, но как только я «runState» для выхода из монады, кажется, что я вернулся к тому, с чего начал, с необходимости отслеживать текущий идентификатор. Я чувствую, что застрял в монаде. Другой вариант, который я рассматривал, заключался в том, чтобы сохранить всю IntMap и текущий «следующий» идентификатор в качестве состояния, но это кажется очень «императивным» и экстремальным. Этот вопрос очень похож, но не получил много ответов (или, может быть, я просто что-то упустил очевидный). Каков идиоматический способ использования монады состояния для получения уникального идентификатора во время выполнения программы? Спасибо.


person MFlamer    schedule 25.01.2013    source источник
comment
На самом деле, чем больше я об этом думаю, идея инкапсулировать всю карту в монаду кажется привлекательной. Я хотел бы написать функции, которые используют идентификатор (или ключ карты) как фактическое значение, вместо того, чтобы постоянно искать. Но также поздно, и я работаю с C++ весь день, поэтому, возможно, я не ясно соображаю.   -  person MFlamer    schedule 25.01.2013
comment
Я думаю, настоящий вопрос в том, почему вы чувствуете, что должны делать что-то помимо runState. Единственная ситуация, когда вы не можете просто вытолкнуть runState наружу, это когда вам нужен доступ к затененной внешней монаде (скажем, IO). В этот момент вам понадобятся трансформаторы, как предлагает Габриэль.   -  person Peter Wortmann    schedule 25.01.2013
comment
@Peter, да, кажется, это настоящая проблема.   -  person MFlamer    schedule 26.01.2013


Ответы (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 в конце, потому что это монада Ttransformer. Мы называем 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.

Чтобы узнать больше о преобразователях монад, я настоятельно рекомендую Преобразователи монад - шаг за шагом.

person Gabriel Gonzalez    schedule 25.01.2013
comment
Как всегда, ответ, наполненный мудростью. Спасибо, Габриэль, мне нужно немного обработать и посмотреть, каковы последствия для моего приложения. - person MFlamer; 25.01.2013
comment
Это была идеальная бумага для меня, чтобы прочитать прямо сейчас. Я знал, что в какой-то момент мне придется покопаться в Monad Transformers, я просто не понимал, как скоро. - person MFlamer; 26.01.2013
comment
@MFlamer Да, Haskell - это создание решений из небольших строительных блоков. Если монады являются строительными блоками языка (т. е. состояние, обработка ошибок, ввод-вывод), то монадные преобразователи являются составными строительными блоками (буквально: сборка монадных преобразователей — это композиция, где монадные преобразователи — это монадные морфизмы между монадными объектами). Когда вы создаете свой собственный стек преобразования монад, вы в основном выбираете, какие языковые функции вы хотите включить, в отличие от других языков, которые делают выбор за вас. - person Gabriel Gonzalez; 27.01.2013