lomeo: (лямбда)
[personal profile] lomeo
Понадобилось мне состояние, вычисление которого можно прервать в любой момент. Комбинатор guard не подходит тем, что его просто нет для State. if/then/else не устравает, потому что иногда прерывать необходимо в любом месте, например, в одной из вызываемой функции рекурсивной функции:
loop = do
    modify (+1)
    check
    loop

check = do
    s <- get
    if s == 42
        then interrupt
        else return ()

Вот как тут описать комбинатор interrupt?

По моему, как не опишешь, зацикливания не избежать. State на этом коде зацикливается. Можно попробовать StateT s Maybe, он оно не вернёт мне состояния на момент прерывания, когда я позову execStateT. Может быть можно всё таки воспользоваться готовым кодом, и просто параметризовать его нужным образом?

Пока обхожусь самодельной монадой
newtype InterruptState s a = IntState { runIntState :: s -> (Maybe a, s) }

Значение монады заворачивается каждый раз в Maybe, Nothing сигнализирует нам о том, что состояние было прервано. Для пользователя работа со значением должна быть прозрачна. Он ничего не обязан знать о том, что наше состояние реализовано через Maybe.
instance Functor (InterruptState s) where
    fmap f (IntState fs) = IntState $ \s -> let (a, s') = fs s
        in maybe (Nothing, s') (\v -> (Just (f v), s')) a

instance Monad (InterruptState s) where
    return x = IntState $ \s -> (Just x, s)
    m >>= f  = IntState $ \s -> let (a, s') = runIntState m s
        -- Вот здесь прерываем обработку, если состояние прервано.
        in maybe (Nothing, s') (\v -> runIntState (f v) s') a

Использование функции maybe позволяет разделить дальнейшее вычисление состояния и прерывание вычисления с возвратом текущего состояния. В качестве бонуса - мы используем чистое значение из под Just-конструктора в дальнейшей обработке.

Реализуем функции работы с состоянием a la State:
instance MonadState s (InterruptState s) where
   get   = IntState $ \s -> (Just s,s)
   put s = IntState $ \_ -> (Just (),s)

execIntState m s = snd (runIntState m s)
evalIntState m s = fst (runIntState m s)

Всяческие withState, mapState реализуются схожим образом. Не забываем только учитывать Nothing.

Прерывание, как уже говорилось, возвращает Nothing в значении.
interrupt = IntState $ \s -> (Nothing, s)

Пример сверху теперь сработает корректно
*Main> execIntState loop 0
42

Обидно, что трансформер, схожий с StateT из этого не сделаешь, чтобы Maybe заменить на Monad m => m. Или сделаешь?

Date: 2009-02-19 07:36 pm (UTC)
From: [identity profile] lomeo.livejournal.com
Зависит от. Для State/StateT хвостато, я где то разбирал даже.

Profile

lomeo: (Default)
Dmitry Antonyuk

April 2024

S M T W T F S
 123456
7891011 1213
14151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 21st, 2025 05:45 am
Powered by Dreamwidth Studios