#haskell #monads #monad-transformers #state-monad
#haskell #монады #монады-трансформеры #состояние-монада
Вопрос:
У меня есть куча функций с отслеживанием состояния внутри монады состояний. В какой-то момент в программе должны быть некоторые действия ввода-вывода, поэтому я заключил ввод-вывод в состояние, получив пару типов, подобных этому:
mostfunctions :: State Sometype a
toplevel :: StateT Sometype IO a
Для простоты я не хочу передавать контекст ввода-вывода в основной набор функций, и я хотел бы избежать обертывания их в тип стека монады. Но для того, чтобы вызвать их из функции верхнего уровня, мне нужно что-то похожее на lift, но я не пытаюсь извлечь значение из внутренней монады. Скорее я хочу преобразовать состояние в монаде StateT во что-то эквивалентное в монаде State. Для этого у меня есть следующее:
wrapST :: (State Sometype a) -> StateT Sometype IO a
wrapST f = do s <- get
let (r,s2) = runState f s
put s2
return r
Затем это используется для чередования таких вещей, как следующее:
toplevel = do liftIO $ Some IO functions
wrapST $ Some state mutations
liftIO $ More IO functions
....
Это кажется довольно очевидным блоком кода, поэтому мне интересно, есть ли у этой функции стандартное имя, и она уже реализована где-то в стандартных библиотеках? Я пытался упростить описание, но, очевидно, это распространяется на извлечение одного преобразователя из стека, преобразование обернутого значения в двоюродный тип transformer, пропуск монад ниже в стеке, а затем возврат результатов обратно в конце.
Комментарии:
1. Разве вы не можете использовать тот факт, что
type State = StateT Identity
? ИспользуйтеStateT Sometype m a
для большого набора функций, чтобы вы могли запускать их как вStateT IO
, так иState
в.
Ответ №1:
Возможно, было бы неплохо провести рефакторинг вашего кода, чтобы использовать тип StateT SomeType m a
вместо State SomeType a
, потому что первый тип совместим с произвольным стеком монад. Если вы измените ее следующим образом, вам больше не понадобится функция wrapST
, поскольку вы можете вызывать функции с отслеживанием состояния напрямую.
Хорошо. Предположим, у вас есть функция subOne :: Monad m => State Int Int
:
subOne = do a <- get
put $ a - 1
return a
Теперь измените типы всех функций, подобных этой, с State SomeType a
на StateT SomeType m a
, оставив m
как есть. Таким образом, ваши функции могут работать с любым монадическим стеком. Для тех функций, которые требуют ввода-вывода, вы можете указать, что монада внизу должна быть IO:
printState :: MonadIO m => StateT Int m ()
printState = do a <- get
liftIO $ print a
Теперь должна быть возможность использовать обе функции вместе:
-- You could use me without IO as well!
subOne :: Monad m => StateT Int m ()
subOne = do a <- get
put $ a - 1
printState :: MonadIO m => StateT Int m ()
printState = do a <- get
liftIO $ print a
toZero :: StateT Int IO ()
toZero = do subOne -- A really pure function
printState -- function may perform IO
a <- get
when (a > 0) toZero
PS: Я использую GHC 7, некоторые библиотеки изменены на полпути, поэтому в GHC 6 это может немного отличаться.
Комментарии:
1. Это не работает, когда я пытаюсь это сделать «Не удалось сопоставить ожидаемый идентификатор типа с предполагаемым типом ввода-вывода». Я не могу поместить фрагмент кода в эти комментарии, не могли бы вы опубликовать простой пример того, что вы имеете в виду?
2. Отлично, это гораздо более простой способ достичь того, что я искал. И это также отвечает на мой первоначальный вопрос: функция, которую я искал, является стандартной частью transformer. кстати, подпись для subOne нуждается в добавлении «Monad m =>» для ghci.
3. Для
printState
иtoZero
было бы неплохо разрешить любую монаду, если ввод-вывод находится в нижней части стека, т. е.printState :: MonadIO m => StateT Int m ()
, или просто удалить подпись типа 🙂4. @hammar: А … забыл об этом.
5. Примечание: В последней версии платформы Haskell состояние определяется в терминах StateT , и то же самое верно для всех монад, которые, я думаю, можно обобщить на монадные преобразователи. До самого недавнего времени это было не так. Это не меняет правильности ответа: StateT все равно следует использовать, потому что он более общий.
Ответ №2:
Более прямой ответ на ваш вопрос: функция hoist
выполняет именно то, что вы описываете, немного более общим способом. Пример использования:
import Control.Monad.State
import Data.Functor.Identity
import Control.Monad.Morph
foo :: State Int Integer
foo = put 1 >> return 1
bar :: StateT Int IO Integer
bar = hoist (return . runIdentity) foo
hoist
является частью MFunctor
класса, который определяется следующим образом:
class MFunctor t where
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
Существуют экземпляры для большинства преобразователей монад, но не ContT
.
Комментарии:
1. Спасибо, это на самом деле ответ, который я изначально искал.