{-# LANGUAGE GADTs, Rank2Types #-}
module Control.Object.Instance where
import Control.Concurrent.MVar
import Control.Object.Object
import Control.Monad.IO.Class
import Control.Monad

-- | MVar-based instance
data Instance f g where
  InstRef :: MVar (Object f g) -> Instance f g
  InstLmap :: (forall x. f x -> g x) -> Instance g h -> Instance f h
  InstRmap :: Instance f g -> (forall x. g x -> h x) -> Instance f h

instance HProfunctor Instance where
  (^>>@) = InstLmap
  (@>>^) = InstRmap

-- | Invoke a method with an explicit landing function.
invoke :: MonadIO m => (forall x. g x -> m x) -> Instance f g -> f a -> m a
invoke m (InstRef v) f = do
  obj <- liftIO (takeMVar v)
  (a, obj') <- m (runObject obj f)
  liftIO $ putMVar v obj'
  return a
invoke m (InstLmap t i) f = invoke m i (t f)
invoke m (InstRmap i t) f = invoke (m . t) i f

-- | Invoke a method.
(.-) :: MonadIO m => Instance f m -> f a -> m a
(.-) = invoke id
{-# INLINE (.-) #-}

-- | Create a new instance.
new :: MonadIO m => Object f g -> m (Instance f g)
new = liftIO . liftM InstRef . newMVar
{-# INLINE new #-}

-- | Create a new instance, having it sitting on the current environment.
newSettle :: MonadIO m => Object f m -> m (Instance f m)
newSettle = new
{-# INLINE newSettle #-}