State Monad с несколькими значениями состояния
Рассмотрим следующее:
do
x1 <- new 2
set x1 3
x2 <- get x1
y1 <- new 10
set y1 20
y2 <- get y1
return (x2 + y2)
Я хочу, чтобы это привело к 23
. Есть ли способ реализовать что-то подобное в чистом Haskell, и если да, то как? Я понимаю, что STRef
делает что-то подобное, но я просто хочу сделать это в обычном Haskell (на данный момент не беспокоюсь об эффективности). Я предполагаю, что мне нужно будет создать тип данных и сделать его экземпляром Monad
, но я не уверен в деталях, поэтому рабочий пример будет полезен.
Ответы
Ответ 1
Это позволяет использовать более одного значения, но оно более волосатое:) Это хорошо упрощено с предложением Даниэля Dynamic
.
import Data.Dynamic
import Data.Maybe
import Control.Monad.State
import Data.Map as M
newtype Ref a = Ref {ref :: Int}
type MutState = State (Int, Map Int Dynamic)
val :: Typeable a => Ref a -> MutState a
val r = snd `fmap` get >>=
return . fromJust . (>>= fromDynamic) . M.lookup (ref r)
new :: Typeable a => a -> MutState (Ref a)
new a = do
(curr, binds) <- get
put (curr + 1, M.insert (curr + 1) (toDyn a) binds)
return . Ref $ curr + 1
set :: Typeable a => Ref a -> a -> MutState ()
set (Ref i) a = do
(c, m) <- get
put (c, M.insert i (toDyn a) m)
runMut :: MutState a -> a
runMut = flip evalState (0, M.fromList [])
Затем, чтобы использовать его
default (Int) -- too lazy for signatures :)
test :: Int
test = runMut $ do
x1 <- new 2
set x1 3
x2 <- val x1
y1 <- new 10
set y1 20
y2 <- val y1
return (x2 + y2)
Ref
в основном Int
с прикрепленной информацией о типе и val
будет искать соответствующий Dynamic
и попытаться заставить его ввести правильный тип.
Если это был реальный код, вы должны скрывать реализации Ref
и MutState
. Для удобства я fromJust
отредактировал возврат val
bur, если вы хотите безопасную реализацию, я полагаю, вы могли бы использовать слои из State
и Maybe
для обработки несвязанных переменных.
И в случае, если вы беспокоитесь о типовых ограничениях, как показано выше, они тривиально получены.
Ответ 2
Существует реализация уже в Control.Monad.State
, но она громоздка для всеобщности: одно усложнение происходит из класса MonadState, а другое из того, что plain State
реализуется с точки зрения более общего StateT
.
Вот пример вашей задачи с использованием этой реализации. Никакой изменчивости не было. Обратите внимание, что ваш пример был вставлен как есть, просто добавив префикс x
:
import Control.Monad.State
import qualified Data.Map as M
type MyMap a = M.Map Int a
type MyState a b = State (MyMap a) b
type MyRef = Int
xrun :: MyState a b -> b
xrun x = evalState x (M.empty)
mget :: MyState a (MyMap a)
mget = get
mput :: MyMap a -> MyState a ()
mput = put
mmodify :: (MyMap a -> MyMap a) -> MyState a ()
mmodify x = modify x
xnew :: s -> MyState s MyRef
xnew val = do
s <- mget
let newRef = if M.null s then 0 else fst (M.findMax s) + 1
mput $ M.insert newRef val s
return newRef
xset :: MyRef -> a -> MyState a ()
xset ref val = modify $ M.insert ref val
xget :: MyRef -> MyState a a
xget ref = fmap (\s -> case M.lookup ref s of Just v -> v) get
test :: MyState Int Int
test = do
x1 <- xnew 2
xset x1 3
x2 <- xget x1
y1 <- xnew 10
xset y1 20
y2 <- xget y1
return (x2 + y2)
main = print $ xrun test
Можно реализовать все функции в модуле и >>=
/return
без использования реализаций запаса от Control.Monad
, сохраняющих подписи.
Вот он:
module MyState (State, get, put, modify, evalState) where
newtype State s a = State (s -> (a, s))
evalState :: State s a -> s -> a
evalState (State f) = fst . f
instance Monad (State s) where
return a = State $ \s -> (a, s)
State f >>= g = State $ \s ->
case f s of
(a', s') -> case g a' of
State h -> h s'
instance Functor (State s) where
fmap f (State g) = State $
\s -> case g s of (a, s) -> (f a, s)
get :: State s s
get = State (\s -> (s, s))
put :: s -> State s ()
put s = State $ \_ -> ((), s)
modify :: (s -> s) -> State s ()
modify f = get >>= put . f
Сохраните его до MyState.hs
и замените import Control.Monad.State
на import MyState
.
Ответ 3
С State
или StateT
вы можете эмулировать его (State
разрешить только 1 значение). Самый простой способ - использовать Map
:
do
put empty
set "x1" 3
x2 <- getKey "x1"
set "y1" 20
y2 <- getKey "y1"
return (x2 + y2)
where
getKey k = fromJust . (lookup k) `fmap` get
set = modify replace
replace d k m = if k `member` m then update (\_ -> Just d) k m
else insert k d m
Ответ 4
Как насчет StateT для кортежа?
flip evalState (2, 10) $ do
modify $ \(_, y) -> (3, y)
x2 <- fst <$> get
modify $ \(x, _) -> (x, 20)
y2 <- snd <$> get
return (x2 + y2)
Если вы действительно хотите использовать изменяемые ячейки, я бы рекомендовал использовать ST, STM или IO вместо StateT. Реализация с использованием StateT над гетерогенной картой от увеличения естественности к объектам представляется возможной, но, вероятно, немного неудобной.