Ранний выход из монады состояния Haskell (охранник?)

У меня есть тип, который представляет состояние игры для моего приложения, для этого вопроса представьте, что это что-то простое, например:

Game { points :: Int }

Я определяю свою игровую логику с помощью монады State.

type GameState a = StateT Game a

addPoints :: Int -> GameState ()
addPoints num = do
    Game p <- get
    put $ Game (p+num)

Я хочу иметь возможность просто отбросить некоторые входные данные

evenResult num = do
    Game p <- get
    return $ even (p + num) 

addPoints num = do
    isEven <- evenResult num
    if isEven then return () else do
    Game n <- get
    put $ Game (n+num)

Мне нужен синтаксис, который выглядит примерно так

addPoints num = do
    guard evenResult
    ...

-- or this
addPoints num = do
    guardIsEvenResult
    ...

Если он попадает в охранник, я хочу, чтобы он оставил состояние в покое и больше ничего не делал в блоке.

Как мне это сделать? Это кажется близким к возможности с MonadPlus, но я не уверен, что смогу использовать mzero, чтобы сказать «вернуть то, что у вас уже есть в вашем состоянии». Спасибо!


person Sean Clark Hess    schedule 23.04.2012    source источник
comment
StateT уже является MonadPlus. Вы пробовали просто использовать Control.Monad.guard?   -  person Lily Ballard    schedule 24.04.2012
comment
@KevinBallard: StateT s m - это только MonadPlus, если m; экземпляр просто поднимает mzero и mplus на один уровень выше.   -  person ehird    schedule 24.04.2012
comment
Control.Monad. Когда ты мужчина? Конечно, досадно, что when хочет значение Bool, а не вычисление, которое дает Bool. Но Рим сгорел не за один день. Если ваш test живет в m Bool, вы могли бы написать (test >>=) . flip when $ action для небольшого недоумения.   -  person pigworker    schedule 24.04.2012


Ответы (2)


Импортируйте Control.Monad.Trans.Maybe и используйте _2 _ поверх StateT. Затем вы можете использовать mzero < / a>, чтобы прервать вычисление или, как говорит Кевин Баллард, guard condition, чтобы остановить, если condition равно False; все, что вам нужно сделать, это заключить каждый блок в runMaybeT. (Обратите внимание, что вам придется либо lift каждую опцию, которую вы определили для своей StateT монады, либо изменить их типы для работы с любой монадой с нужным вам состоянием, например operation :: (MonadState m Game) => ....)

Обратите внимание, что у вас, вероятно, есть пакет transformers, в котором содержится Control.Monad.Trans.Maybe, даже если вы не используете его напрямую; пакет mtl, который содержит стандартные модули монад, такие как Control.Monad.State, зависит от него.

person ehird    schedule 23.04.2012

Я думаю, что уточняю комментарий свинарника по этому вопросу.

ensure :: GameState Bool -> GameState () -> GameState ()
ensure p k = do 
  t <- p 
  when t k

addPoints num = do
  ensure (evenResult num) $ do
  ...

Что достаточно близко. Я уверен, что ответ ehird более правильный, но он также кажется намного сложнее, чем я хотел. Столько всего еще предстоит узнать :)

person Sean Clark Hess    schedule 24.04.2012
comment
В качестве альтернативы я мог бы написать гарантию как ensure :: (Game -> Bool) -> GameState () -> GameState (), а все мои функции предиката как whatever :: Game -> Bool вместо whatever :: GameState Bool - person Sean Clark Hess; 24.04.2012