Подтвердить что ты не робот

Объединить состояние с действиями IO

Предположим, что у меня есть монада состояний, такая как:

data Registers = Reg {...}

data ST = ST {registers :: Registers,
              memory    :: Array Int Int}

newtype Op a = Op {runOp :: ST -> (ST, a)}

instance Monad Op where
 return a    = Op $ \st -> (st, a)
 (>>=) stf f = Op $ \st -> let (st1, a1) = runOp stf st
                               (st2, a2) = runOp (f a1) st1
                            in (st2, a2)

с функциями типа

getState :: (ST -> a) -> Op a
getState g = Op (\st -> (st, g st)

updState :: (ST -> ST) -> Op ()
updState g = Op (\st -> (g st, ()))

и т.д. Я хочу объединить различные операции в этой монаде с действиями IO. Поэтому я мог бы написать цикл оценки, в котором выполнялись операции в этой монаде, и с результатом выполнялось действие IO или, я думаю, я мог бы сделать что-то вроде следующего:

newtype Op a = Op {runOp :: ST -> IO (ST, a)}

Функции печати будут иметь тип Op(), а другие функции будут иметь тип Op a, например, я мог бы прочитать символ из терминала, используя функцию типа IO Char. Однако я не знаю, как выглядит такая функция, поскольку, например, недопустимо следующее.

runOp (do x <- getLine; setMem 10 ... (read x :: Int) ... ) st

поскольку getLine имеет тип IO Char, но это выражение будет иметь тип Op Char. В общих чертах, как бы я это сделал?

4b9b3361

Ответ 1

Основной подход - переписать вашу монаду Op как монадный трансформатор. Это позволит вам использовать его в "стеке" монадов, нижняя часть которых может быть IO.

Вот пример того, как это может выглядеть:

import Data.Array
import Control.Monad.Trans

data Registers = Reg { foo :: Int }

data ST = ST {registers :: Registers,
              memory    :: Array Int Int}

newtype Op m a = Op {runOp :: ST -> m (ST, a)}

instance Monad m => Monad (Op m) where
 return a    = Op $ \st -> return (st, a)
 (>>=) stf f = Op $ \st -> do (st1, a1) <- runOp stf st
                              (st2, a2) <- runOp (f a1) st1
                              return (st2, a2)

instance MonadTrans Op where
  lift m = Op $ \st -> do a <- m
                          return (st, a)

getState :: Monad m => (ST -> a) -> Op m a
getState g = Op $ \st -> return (st, g st)

updState :: Monad m => (ST -> ST) -> Op m ()
updState g = Op $ \st -> return (g st, ())

testOpIO :: Op IO String
testOpIO = do x <- lift getLine
              return x

test = runOp testOpIO

Ключевое значение для наблюдения:

  • Использование класса MonadTrans
  • Использование функции lift, действующей на getLine, которая используется для вывода функции getLine из монады IO и в монаду Op IO.

Кстати, если вы не хотите, чтобы монада IO всегда присутствовала, вы можете заменить ее на монаду Identity в Control.Monad.Identity. Монада Op Identity ведет себя точно так же, как ваша оригинальная монада Op.

Ответ 2

Использовать liftIO

Ты уже очень близко! Ваше предложение

newtype Op a = Op {runOp :: ST -> IO (ST, a)}

отличный и способ пойти.

Чтобы выполнить getLine в контексте Op, вам нужно "поднять" операцию IO в монаду Op. Вы можете сделать это, написав функцию liftIO:

liftIO :: IO a -> Op a
liftIO io = Op $ \st -> do
  x <- io
  return (st, x)

Теперь вы можете написать:

runOp (do x <- liftIO getLine; ...

Использовать класс MonadIO

Теперь шаблон подъема действия ввода-вывода в пользовательскую монаду настолько распространен, что для него есть класс стандартного типа:

import Control.Monad.Trans

class Monad m => MonadIO m where
  liftIO :: IO a -> m a

Итак, ваша версия liftIO будет вместо экземпляра MonadIO:

instance MonadIO Op where
  liftIO = ...

Использовать StateT

В настоящее время вы создали собственную версию государственной монады, специализирующейся на состоянии ST. Почему бы вам не использовать стандартную государственную монаду? Это избавит вас от необходимости писать свой собственный экземпляр Monad, который всегда остается неизменным для государственной монады.

type Op = StateT ST IO

StateT уже имеет экземпляр Monad и экземпляр MonadIO, поэтому вы можете использовать их немедленно.

Трансформаторы Monad

StateT является так называемым монадным трансформатором. Вам нужны только действия IO в вашей монаде Op, поэтому я уже специализировал ее с монадой IO для вас (см. Определение type Op). Но монадные трансформаторы позволяют вам складывать произвольные монады. Об этом говорит intoverflow. Вы можете прочитать о них здесь и здесь.