Как называется эта функция стека монады?

#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. Спасибо, это на самом деле ответ, который я изначально искал.