monads-tf: экземпляр MonadReader для MonadState - PullRequest
3 голосов
/ 03 сентября 2011

Рассмотрим следующий пример. У меня есть монада MyM, которая просто StateT

{-# LANGUAGE TypeFamilies #-}

import Control.Monad.State
import Control.Monad.Reader

type MyS = Int
type MyM = StateT MyS

Обычно MyM используется для чтения и записи MyS состояния, поэтому у меня есть такие функции:

f1 :: (MonadState m, StateType m ~ MyS) => m ()
f1 = modify (+1)

Но иногда мне нужно просто прочитать MyS, поэтому я хочу MonadReader context вместо MonadState:

f2 :: (MonadReader m, EnvType m ~ MyS) => m Int
f2 = liftM (+1) ask

И я хочу написать что-то вроде:

f3 :: (MonadState m, StateType m ~ MyS) => m Int
f3 = f1 >> f2

Так что в принципе мне нужно, чтобы каждый экземпляр MonadState тоже был MonadReader с типом соответствующей семьи. Что-то вроде

instance MonadState m => MonadReader where
  type EnvType m = StateType m
  ...

Но я не могу найти способ, как заставить его проверять тип. Можно ли выразить такое отношение между MonadState и MonadReader?

Спасибо.

1 Ответ

4 голосов
/ 03 сентября 2011

Звучит так, будто вы хотите, чтобы ask имел тот же эффект, что и get. Я не могу не задаться вопросом, почему вы просто не используете get в этом случае:)

Если ваша цель - написать код, который либо читает состояние, либо env в зависимости от того, что доступно, вы должны спросить, что вы планируете делать, скажем, ReaderT r (StateT s m) a, где у вас есть оба. По этой причине вы не можете просто сделать:

instance MonadState m => MonadReader m where
  type EnvType m = StateType m
  ask = get

потому что вы будете конфликтовать с существующими экземплярами. Вы можете, однако, сделать:

{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies #-}
newtype ReadState m a = RS { unRS :: m a }
  deriving (Monad)

instance MonadState m => MonadReader (ReadState m) where
  type EnvType (ReadState m) = StateType m
  ask = RS get
  local f (RS m) = RS $ do
    s <- get
    modify f
    x <- m
    put s
    return x

Тогда, если у вас есть полиморфное значение чтения, такое как f2, вы можете извлечь из него MonadState с помощью unRS. Если вы хотите использовать более хитрые расширения, попробуйте это с RankNTypes:

useStateAsEnv :: (MonadState n) => (forall m . (MonadReader m, EnvType m ~ StateType n) => m a) -> n a
useStateAsEnv m = unRS m

Тогда вы можете сделать useStateAsEnv (liftM (+1) ask) и получить значение MonadState. Однако я не до конца исследовал, насколько это полезно на практике - для получения значения типа forall m. MonadReader m => m a вы можете в значительной степени использовать только ask, local и монадические функции.

Вот аналогичная, менее общая, но, вероятно, более полезная вещь, использующая стандартные трансформаторы:

readerToState :: (Monad m) => ReaderT r m a -> StateT r m a
readerToState reader = StateT $ \env -> do
  res <- runReaderT reader env
  return (res, env)

Редактировать: подумав об этом позже, вы, вероятно, могли бы обобщить это для любого преобразователя монад состояния с помощью lift . runReaderT reader =<< get, но тип начинает быть довольно громоздким:

:: (Monad m, MonadTrans t, MonadState (t m)) => ReaderT (StateType (t m)) m b -> t m b

который является обобщением вышеупомянутого, но на самом деле не может быть полезным.

...