packages feed

eternal-0.1.3: src/Control/Eternal/Reactive.hs

{-# LANGUAGE
    UnicodeSyntax
  , RankNTypes
  , GADTs
  , ScopedTypeVariables
  , LambdaCase
  , Safe
  #-}

module Control.Eternal.Reactive
    ( Action
    , Request
    , reactiveObjectIO
    , Sink
    , pauseIO
    , reactiveIO
    ) where

-- |
-- Module: Control.Eternal.Reactive
-- Copyright: Andy Gill (??-2008), Heather Cynede (2014-??)
-- License: BSD3
-- |

import Control.Concurrent.Chan
import Control.Concurrent

-- An action is an IO-based change to an explicit state
type Action s    = s → IO s     -- only state change
type Request s a = s → IO (s,a) -- state change + reply to be passed back to caller

-- Choices:
--   * do the Requests see the failure
--   * Actions do not see anything
--   *
data Msg s = Act (Action s)
           | ∀ a . Req (Request s a) (MVar a)
           | Done (MVar ())

reactiveObjectIO
    :: ∀ state object. state
    → ( ThreadId
            → (∀ r. Request state r → IO r)     -- requests
            → (Action state → IO ())             -- actions
            → IO ()                                -- done
            → object
       ) → IO object
reactiveObjectIO state mkObject = do
    chan ← newChan
    let dispatch st =
         readChan chan >>= \case Act act     → do state1 ← act st
                                                  dispatch $! state1
                                 Req req box → do (state1,ret) ← req st
                                                  putMVar box ret
                                                  dispatch $! state1
                                 Done box    → do putMVar box ()
                                                  return ()
        requestit :: ∀ r. Request state r → IO r
        requestit fun = do ret ← newEmptyMVar
                           writeChan chan $ Req fun ret
                           takeMVar ret -- wait
        actionit act = writeChan chan $ Act act
        doneit = do ret ← newEmptyMVar
                    writeChan chan $ Done ret
                    takeMVar ret -- wait
    pid ← forkIO $ dispatch state
    return (mkObject pid requestit actionit doneit)

type Sink a = a → IO ()

-- This turns a reactive style call into a pausing IO call.
pauseIO :: (a → Sink b → IO ()) → a → IO b
pauseIO fn a = do var ← newEmptyMVar
                  forkIO $ fn a (putMVar var)
                  takeMVar var

-- This turns a pausing IO call into a reactive style call.
reactiveIO :: (a → IO b) → a → Sink b → IO ()
reactiveIO fn a sinkB = do forkIO $ sinkB =<< fn a
                           return ()