LINUX.ORG.RU

Глобальное состояние в Haskell


0

1

Ситуация такова: допустим, есть либа, обслуживающая некоторый протокол. И на различные приходящие команды нужно дёргать за функции-коллбэки. Пусть мы храним некоторую структуру, хранящую пары событие-функция. Эта структура инициализируется дефолтными значениями. Теперь 2 ситуации:

1. Мы до запуска основного цикла обработки заменяем дефолтные обработчики своими и в процессе работы цикла не меняем их больше.

2. Меняем обработчики по ходу работы цикла, например где-то в функциях-обработчиках.

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

А вот как быть во втором случае, как кошернее это реализовать?


Передавать в и возвращать из функций обработчиков структуру с обработчиками и использовать, возвращённую структуру для дальшейшей работы.

Begemoth ★★★★★ ()

Почитай лучше про Functional Reactive Programming на хаскелл.орг

grassmeister ()

mtl, в частности, Control.Monad.State. Пример:

{-# LANGUAGE MultiParamTypeClasses #-}

import Data.IORef
import Control.Monad.State

data MyHandler = MyHandler
  { handler :: Int -> Int
  }

newtype MyHandlerT m a = MyHandlerT { fromMyHandlerT :: IORef MyHandler -> m a }

instance MonadTrans MyHandlerT where
  lift = MyHandlerT . const

instance MonadIO m => Monad (MyHandlerT m) where
  return = MyHandlerT . const . return
  MyHandlerT m >>= k = MyHandlerT $ 
    \r -> do
      x <- m r
      let MyHandlerT m' = k x in m' r
  MyHandlerT m >> MyHandlerT m' = MyHandlerT $ \r -> m r >> m' r
  fail = error

instance MonadIO m => MonadState MyHandler (MyHandlerT m) where
  get = MyHandlerT $ liftIO . readIORef
  put s = MyHandlerT $ liftIO . flip writeIORef s

myDefaultHandler :: MyHandler
myDefaultHandler = MyHandler 
  { handler = (+ 1)
  }

runMyHandlerT :: MonadIO m => MyHandlerT m a -> m a
runMyHandlerT m = liftIO (newIORef myDefaultHandler) >>= fromMyHandlerT m

test :: MyHandlerT IO ()
test = do
  h <- gets handler
  lift $ print $ h 0
  put $ myDefaultHandler { handler = (+ 2) }
  h <- gets handler
  lift $ print $ h 0

main :: IO ()
main = runMyHandlerT test

(можно этого не делать, а просто сразу использовать StateT - этот пример отличается тем, что хранит состояние в IORef и делает на ней readIORef / writeIORef). Более серьёзный пример - type checking monad transformer из agda.

quasimoto ★★★★ ()
Ответ на: комментарий от quasimoto

put $ myDefaultHandler { handler = (+ 2) }

Или так:

  modify $ \oldHandler -> oldHandler { handler = ... }
quasimoto ★★★★ ()
Ответ на: комментарий от quasimoto

Ещё в happstack State / Reader / Writer активно используются. Ну и много ещё где.

quasimoto ★★★★ ()

- Поменять модель: калбэки расширить до стрелочек (пример: grapefruit вместо gtk напрямую).

- Использовать rec (MonadFix instance для IO) и «перебиндивать» при необходимости, практичнее, но костыльнее.

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.