packages feed

reactor-0.1.3: Reactor/Observable.hs

module Reactor.Observable
  ( Observable(..)
  , never
  , fby
  , take, drop
  , safe
  , filterMap, (!?)
  , observe
  , counted
  -- , andThen
  , (<||>)
  -- , feed
  -- , head
  -- , safeHead
  ) where

import Prelude hiding (filter, take, drop)
import Control.Applicative
import Control.Exception hiding (handle)
import Control.Monad
import Control.Monad.Error
import Data.Foldable
import qualified Data.Foldable as Foldable
import Data.Functor.Bind
import Data.Functor.Plus
import Data.Functor.Extend
import Data.Functor.Contravariant
import Data.Monoid
import Data.IORef
import Reactor.Atomic
import Reactor.Filtered
import Reactor.Observer
import Reactor.Task
import Reactor.Subscription

newtype Observable a = Observable { subscribe :: Observer a -> Task Subscription }

instance Functor Observable where
  fmap f s = Observable (subscribe s . contramap f)

instance Apply Observable where
  mf <.> ma = Observable $ \o -> do
    funs <- io $ newIORef []
    fflag <- atomic (1 :: Int)
    aflag <- atomic (1 :: Int)
    let discard pf sf = do
          i <- atomicFetchAndAnd pf 0
          when (i == 1) $ do
            j <- atomicFetch sf
            when (j == 0) $ complete o
    mappend 
      <$> subscribe_ mf
            (\f -> io $ atomicModifyIORef funs (\fs -> (f:fs, ())))
            (handle o)
            (discard fflag aflag)
      <*> subscribe_ ma
            (\a -> do
              fs <- io $ readIORef funs 
              spawn $ Foldable.mapM_ (\f -> o ! f a) fs)
            (handle o)
            (discard aflag fflag)

instance Filtered Observable where
  filter p s = Observable (subscribe s . filter p)

instance Applicative Observable where
  pure a = Observable $ \o -> (o ! a) *> complete o $> mempty 
  (<*>) = (<.>)

instance Bind Observable where
  mf >>- k = Observable $ \o -> do
    counter <- atomic (1 :: Int)
    topFlag <- atomic (1 :: Int)
    let detach flag = do
          clearing flag $ do
            i <- atomicSubAndFetch counter 1
            when (i == 0) $ complete o
    subscribe_ mf 
      (\f -> do
        flag <- atomic (1 :: Int)
        _ <- atomicAddAndFetch counter 1 
        () <$ subscribe_ (k f)
          (o !)
          (handle o)
          (detach flag))
      (handle o)
      (detach topFlag)
        
instance Monad Observable where
  return = pure
  (>>=) = (>>-)

instance Alt Observable where
  a <!> b = Observable $ \o -> subscribe_ a
    (o !)
    (handle o)
    (subscribe b o $> ())

instance Plus Observable where
  zero = Observable (\o -> complete o $> mempty)
  
instance Alternative Observable where
  empty = zero
  (<|>) = (<!>)

instance MonadPlus Observable where
  mzero = zero
  mplus = (<!>)

instance Extend Observable where
  duplicate p = Observable $ \o -> subscribe_ p 
    (\a -> o ! fby a p)
    (handle o) 
    (complete o)
  extend f p = Observable $ \o -> subscribe_ p 
    (\a -> o ! f (fby a p))
    (handle o)
    (complete o)

safe :: Observable a -> Observable a 
safe p = Observable $ \o -> do
   alive <- atomic (1 :: Int)
   subscribe_ p
     (\a -> given alive $ o ! a)
     (\e -> clearing alive $ handle o e)
     (clearing alive $ complete o)

subscribe_ :: Observable a -> (a -> Task ()) -> (SomeException -> Task ()) -> Task () -> Task Subscription
subscribe_ a f h c = subscribe a (Observer f h c)

filterMap :: (a -> Maybe b) -> Observable a -> Observable b
filterMap p s = Observable $ \o -> subscribe s $ o ?! p

(!?) :: Observable a -> (a -> Maybe b) -> Observable b
(!?) = flip filterMap 

never :: Observable a
never = Observable $ \_ -> return mempty

fby :: a -> Observable a -> Observable a 
fby a as = Observable $ \o -> do
  o ! a
  subscribe as o

take :: Int -> Observable a -> Observable a 
take n p = Observable $ \o -> do
  counter <- atomic n
  subscribe_ p 
    (\a -> do
      i <- io $ atomicSubAndFetch counter 1
      when (i >= 0) $ o ! a
      when (i == 0) $ complete o)
    (handle o)
    (do
      i <- io $ atomicFetchAndAnd counter 0
      when (i >= 0) $ complete o)
      
drop :: Int -> Observable a -> Observable a 
drop n p = Observable $ \o -> do
  counter <- atomic n
  subscribe_ p
    (\a -> do
      i <- io $ atomicSubAndFetch counter 1
      when (i < 0) $ o ! a)
    (handle o)
    (complete o)

-- | Observe both at the same time.
(<||>) :: Observable a -> Observable a -> Observable a 
p <||> q = Observable $ \o -> mappend <$> subscribe p o <*> subscribe q o

observe :: Foldable f => f a -> Observable a
observe t = Observable $ \o -> do
  spawn $ do
    Foldable.forM_ t (o !)
    complete o
  return mempty

counted :: Observable a -> Observable (Int, a)
counted p = Observable $ \o -> do
  counter <- atomic 0
  subscribe_ p 
    (\a -> do
      i <- atomicFetchAndAdd counter 1
      o ! (i, a))
    (handle o)
    (complete o)

-- do something when the observable completes

{-
andThen :: Observable a -> Task () -> Task ()
andThen p t = spawn $ do
  () <$ subscribe_ p (\_ -> return ()) (\_ -> t) t
-}

{-

feed :: Moore i o -> Observable i -> Task o
feed machine p = do
  m <- io $ newIORef machine
  callCC $ \resume -> 
    subscribe p 
      (\i -> io $ atomicModifyIORef m $ \m' -> (step m' i, ()))
      throwError 
      (do Moore _ o <- io $ readIORef m
          resume o)

safeHead :: Onservable a -> Task (Maybe a)
safeHead = feed (Moore (pure . Just) Nothing) . take 1

head :: Observable a -> Task a 
head = feed (Moore pure (error "head: empty observable")) . take 1

-- uncons :: Observable a -> Task (Maybe (a, Observable a))
-}