Haskell'deki bir tasarım problemiyle uğraşıyorum, zarif ve tatmin edici bir şekilde çözemiyorum. Özünde, olay kaynağı olan kavramına dayanan bir sistem var: sistemin durumu, bir olay dizisinin ilk duruma getirilmesiyle sonuçlanır.Haskell bir olay otobüs nasıl yazılır?
class Model a where
data Event a :: *
apply :: Event a -> a -> a
instance Model Foo where
data Event Foo = Foo Int
...
instance Model Bar where
data Event Bar = Bar String
...
anda sistem% 100 senkron ve birleştiğinde ise, tüm diğer modelin olaylara erişimi olan her modeli ve: Olayların farklı türleri her tür türüdür ailesi aracılığıyla sistemin belirli bir bileşenine ilişkin ediliyor vardır şekildeBus Events
bazı olmadığı varsayımıyla bir Bus Events
için Event Foo
bazı tüketici takmak için dispatch :: Bus Events -> Consumer (Event Foo) -> Bus Events
gibi bir şey yazmak gerekir bu hızla bir karmaşa haline geliyor, bu yüzden bir olay otobüsünün tanıtım yoluyla şeyleri ayrıştırmak istiyor Event Foo
ve Events
arasında alt tip veya alt bölümün şekli. Daha sonra, tüketicilerin her birinin kendi iş parçacıklarında çalışmasını sağlayarak uyumsuzluk ekleyebilirim.
Sistem açısından, bu, her bileşenin bağımsız olarak paketlenebilir olmasını ve bağımlılıkları tüm olayların bir alt kümesine sınırladığından emin olmamı sağlar. Tüm uygulama düzeyinde Events
tipi tanımlanacaktır. Bu sorun, ayrık zamanlı FRP'ye aldatıcı bir şekilde benziyor, ancak kafamın etrafını saracak gibi görünmüyorum ...
Daha önce benzer bir şeyle uğraşan var mı ve evetse, nasıl?
DÜZENLEME:
Ben Source
hiçbir kullanır ama büyük ölçüde @ tarafından Cirdec önerisini ilham aşağıdaki kodla geldi: a
s taşıyan olayların
import Control.Applicative
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.Reader
import qualified Data.Vector as V
type Handlers e = V.Vector (Handler e)
data EventBus e = EventBus { handlers :: Handlers e
, eventQueue :: TChan e
, eventThread :: MVar ThreadId
}
newBus :: IO (EventBus e)
newBus = do
chan <- newTChanIO
var <- newEmptyMVar
return $ EventBus V.empty chan var
addHandler :: Handler e -> EventBus e -> EventBus e
addHandler h [email protected]{..} = b { handlers = V.snoc handlers h }
removeHandler :: Int -> EventBus e -> EventBus e
removeHandler idx [email protected]{..} = b { handlers = let (h,t) = V.splitAt idx handlers
in h V.++ V.tail t }
startBus :: EventBus e -> IO (EventBus e)
startBus [email protected]{..} = do
tid <- forkIO (runBus b)
putMVar eventThread tid
return b
runBus :: EventBus e -> IO()
runBus [email protected]{..} = do
_ <- takeMVar eventThread
forever $ do
e <- liftIO $ atomically $ readTChan eventQueue
v <- newTVarIO b
runReaderT (runEvents $ publish e) v
-- | A monad to handle pub/sub of events of type @[email protected]
newtype Events e a = Events { runEvents :: ReaderT (TVar (EventBus e)) IO a }
deriving (Applicative, Functor, Monad, MonadIO, MonadReader (TVar (EventBus e)))
newtype Handler e = Handler { handle :: Events e() -- Unsubscription function
-> Events e (e -> Events e()) -- what to do with events @[email protected]
}
-- | Register a new @Handler [email protected] within given @Events [email protected] context
subscribe :: Handler e -> Events e()
subscribe h = do
bus <- ask
liftIO $ atomically $ modifyTVar' bus (addHandler h)
unsubscribe :: Int -> Events e()
unsubscribe idx = do
bus <- ask
liftIO $ atomically $ modifyTVar' bus (removeHandler idx)
publishBus :: EventBus e -> e -> IO()
publishBus EventBus{..} = atomically . writeTChan eventQueue
publish :: e -> Events e()
publish event = do
EventBus{..} <- ask >>= liftIO . atomically . readTVar
forM_ (zip (V.toList handlers) [0..]) (dispatch event)
dispatch :: e -> (Handler e, Int) -> Events e()
dispatch event (Handler h, idx) = do
hdl <- h (unsubscribe idx)
hdl event
printer :: (Show s) => String -> Handler s
printer prefix = Handler (\ _ -> return $ \ e -> liftIO (putStrLn $ prefix ++ show e))
Bu nedenle etkinlikleriniz için Gözlenebilir desen gibi bir şey mi arıyorsunuz? IMO'yu tıpkı OO'da yaptığınız gibi (“IORef” i kullanarak veya “İsterseniz” ya da “Abone ol” gibi davranarak) ya da eğer istemiyorsanız, işleyicileri yaratırken yapabilirsiniz. Buna ihtiyacınız var mı, yoksa * komutlarınız/-handler * sizin için sorumlu mu? – Carsten
@Carsten, evet.Gerçek tesisat için pub/sub kabiliyetlerini sağlamanın iyi bir özelliği olan 'TChan'ı kullanmayı planlıyorum ama bu gerçekten benim sorunum değil. Genel tasarım yaklaşımı hakkında daha çok endişeliyim, bu, yazılan aktörler gibi bir şeyi uygulamaya çalışıyorum ya da ayrık zamanlı FRP'yi yeniden icat etmeye çalışıyorum gibi görünüyor ama kafam karıştı. – insitu
Çözmeye çalıştığınız sorun nedir? Sorunuz, sorunun nasıl çözüleceği değil, belirli bir yolla nasıl çözüleceğini sormaktır. Çözüm zaten mevcut olan bir şeydir: oo stili olaylar, gözlenebilirler, FRP veya eşzamanlı borular. – Cirdec