2015-09-07 30 views
8

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)) 
+0

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

+0

@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

+0

Çö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

cevap

3

bir kaynak olduğunu abone olunabilir, aşağıdaki türüne sahip olabilir:

Bir tüketici veya el Olayların ler safça bir olay kaynağı kabul eder ve ona abone şeydir

type Handler m a = (Source m a   ) -> m() 
       = ((a -> m()) -> m (m())) -> m() 
               ^-- set up the consumer. 

Bu bir şeyleri ters ve bir olay işleyicisi için güzel temsil alabilir, biraz kıvrık:

type Handler m a = m() -> m (a -> m()) 
        |  | ^-- what to do when an `a` happens 
        |  ^-- set up the consumer 
        ^-- how to unsubscribe 

The Orijinal olay kaynağı kullanmak biraz zordu; Bir abone, gerçekleşen bir olaya yanıt olarak aboneliği iptal etmek isteyebilir; bu durumda, sonuçta ortaya çıkan abonelikten çıkma eylemini, bir olay gerçekleştiğinde ne yapılması gerektiğine tekrar tekrar eklemesi gerekir. Handler'un daha güzel tanımından başlayarak bu sorunumuz yok. Bir etkinlik kaynağı artık bir etkinlik işleyicisini kabul eden ve yayınlayan bir şey.

type Source m a = (Handler m a   ) -> m() 
       = (m() -> m (a -> m())) -> m() 
              ^-- how to subscribe 
+1

Öneriniz için teşekkürler, eksik olan kısım haricinde aradığım şeye benziyor: “Handler ma” tanımlaması, “Kaynak mb” ye geçebilecek şekilde: a: insitu

+0

Aslında yapmaya çalıştığım şey bu kadar basit görünmüyor çünkü bazılarını zorlayabilmek anlamına geliyor. 'b' şeklinde bir toplam türüne sonra ilgili" İşleyici c "ye gönderilir, böylece a: insitu