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


person Clinton    schedule 07.10.2013    source источник
comment
Ваш вопрос касается только реализации изменяемых ссылок или вам также нужно научиться реализовывать монаду с чистым состоянием?   -  person hugomg    schedule 07.10.2013
comment
Чистое состояние. Нет необходимости в фактической изменяемой реализации. Если это уже существует, ссылка на страницу взлома подойдет. Проблема с этим: hackage.haskell .org/package/mtl-2.1.2/docs/ заключается в том, что, кажется, можно put все состояние, а не конкретную переменную   -  person Clinton    schedule 07.10.2013
comment
В вашем коде x1 и y1 кажутся переменными, а x2 и y2 кажутся значениями; у них должны быть разные типы, поэтому я нахожу название удивительным. Вы знаете об этом?   -  person Joachim Breitner    schedule 07.10.2013
comment
Я не знал, но да, я понимаю вашу точку зрения. Название обычное, но я не буду его менять сейчас.   -  person Clinton    schedule 07.10.2013
comment
Не знаю, что вы имеете в виду под обычным. В Haskell переменная типа Integer — это нечто совершенно отличное от именованной ссылки в некотором состоянии; Я действительно предлагаю не давать запутанных имен.   -  person Joachim Breitner    schedule 07.10.2013
comment
Извините, под обычным я подразумеваю плохое. Я был свободен со своим английским.   -  person Clinton    schedule 07.10.2013
comment
Ах я вижу. На самом деле «обычный» означает «обычный», «обычный», так что совсем наоборот :-)   -  person Joachim Breitner    schedule 07.10.2013
comment
Я использовал его в более низком качестве; второсортный смысл, но я вижу путаницу.   -  person Clinton    schedule 07.10.2013


Ответы (4)


Это позволяет использовать более одного значения, но это сложнее :) Это хорошо упрощается с предложением Даниэля 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 , но если вам нужна безопасная реализация, я полагаю, вы могли бы наслаивать монады State и Maybe для работы с несвязанными переменными.

И если вас беспокоят типизируемые ограничения, как показано выше, они получаются тривиально.

person Daniel Gratzer    schedule 07.10.2013
comment
Зачем повторно реализовывать Dynamic? - person Daniel Wagner; 07.10.2013
comment
@DanielWagner Упрощенно, спасибо! вы знаете, что однажды я опубликую ответ без вашего комментария, показывающего, насколько он излишне сложен;) - person Daniel Gratzer; 07.10.2013

В Control.Monad.State уже есть реализация, но она громоздка из соображений общности: одна сложность связана с классом MonadState, а другая — с тем фактом, что простая 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.

person nponeccop    schedule 07.10.2013
comment
У меня тоже был этот ответ первым (за него проголосовали, а затем я удалил его), но если вы присмотритесь, то увидите, что Клинтон хочет отслеживать переменные в состоянии, а не только одно государство, так что что-то вроде монады ST. - person Joachim Breitner; 07.10.2013
comment
Я думаю, что что-то вроде type MyState a b = State (M.Map Int a, Int) b сделает за него эту работу. сейчас уточняю ответ - person nponeccop; 07.10.2013
comment
это то, что я написал ниже. Обратите внимание, что вам не нужен второй Int, вы можете легко (и эффективно) получить самый большой индекс в карте. - person Joachim Breitner; 07.10.2013

С помощью 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
person wit    schedule 07.10.2013
comment
Я бы предпочел не выполнять поиск на карте. Я знаю, что сказал, что не беспокоюсь об эффективности, но это похоже на лайфхак. - person Clinton; 07.10.2013
comment
@Clinton, если у вас есть 200 .. значений, это неэффективно. Это всего лишь O(log n) - person wit; 07.10.2013

Как насчет 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 на неоднородной карте от увеличения натуральных чисел до объектов кажется возможной, но, вероятно, немного неудобной.

person Boyd Stephen Smith Jr.    schedule 17.10.2013
comment
Интересно, что \(_, y) -> (3, y) совпадает с (first . const) 3. - person Micha Wiedenmann; 09.03.2020