{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, UndecidableInstances, TypeFamilies, FunctionalDependencies #-} ----------------------------------------------------------------------------- -- | -- Module : Control.Monad.Imperative.Internals -- Maintainer : Matthew Mirman -- Stability : experimental -- Portability : GADTs, EmptyDataDecls, GeneralizedNewtypeDeriving, -- MultiParamTypeClasses, FlexibleInstances -- -- A module which defines the monad for ImperativeHaskell, -- and some control operator to interact with 'MIO' ----------------------------------------------------------------------------- module Control.Monad.Imperative.Internals ( modifyOp , if' , for' , while' , break' , continue' , defer' , function , new , auto , runImperative , io , V(Lit, C) , MIO() , TyInLoop , TyInFunc , TyVar , TyVal , TyComp , (=:) , (&) , HasValue(..) , CState(return') ) where import Data.Functor import Control.Monad.Cont import Control.Monad.State import Data.IORef import Data.String (IsString(..)) data TyInLoop data TyInFunc data TyVar data TyVal data TyComp i v type RCont r = ContT r IO type MIO_I i r a = StateT (Control i r) (RCont r) a type RetCont r = r -> RCont r () newtype MIO i r a = MIO { getMIO :: MIO_I i r a } deriving (Monad, MonadCont, MonadIO) data V b r a where R :: IORef a -> V TyVar r a Lit :: a -> V TyVal r a C :: MIO i r (V b r a) -> V (TyComp i b) r a data Control i r where InFunction :: RetCont r -> Control TyInFunc r InLoop :: MIO TyInLoop r () -> MIO TyInLoop r () -> RetCont r -> Control i r getReturn :: Control i r -> RetCont r getReturn (InFunction ret) = ret getReturn (InLoop _ _ ret) = ret -- | Although the functional dependency @b -> i@ is declared, -- it does not do anything useful. class HasValue r b i | b -> r i where val :: b a -> MIO i r a instance HasValue r (V TyVar r) i where val (R r) = MIO $ liftIO $ readIORef r instance HasValue r (V TyVal r) i where val (Lit v) = return v instance HasValue r (V b r) a => HasValue r (V (TyComp a b) r) a where val (C m) = val =<< m instance HasValue r (MIO i r) i where val m = m instance HasValue r IO i where val m = liftIO m class CState i where type RetTy i a getState :: MIO i r (Control i r) -- | @'return'' value@ acts like an imperative return. It passes -- the given value to the return continuation. return' :: HasValue r (V a r) i => V a r r -> MIO i r (RetTy i r) toLoop :: MIO i r a -> MIO TyInLoop r a instance CState TyInFunc where type RetTy TyInFunc a = a getState = MIO get return' v = MIO $ do v' <- getMIO $ val v InFunction ret <- get lift $ ret v' return v' toLoop (MIO m) = MIO $ wrapState m statefulRetCont $ \(InLoop _ _ retLoop) -> InFunction retLoop instance CState TyInLoop where type RetTy TyInLoop a = () getState = MIO get return' v = MIO $ do v' <- getMIO $ val v InLoop _ _ ret <- get lift $ ret v' return () toLoop m = m statefulRetCont :: Control t r -> Control i r -> Control t r statefulRetCont (InLoop a b _) = InLoop a b . getReturn statefulRetCont (InFunction _) = InFunction . getReturn -- | @'for''(init, check, incr)@ acts like its imperative @for@ counterpart for' :: (CState i, HasValue r (V b r) i, HasValue r valt TyInLoop) => (MIO i r irr1, V b r Bool, MIO i r irr2) -> valt () -> MIO i r () for' (init, check, incr) body = init >> for_r where for_r = do do_comp <- val check when do_comp $ callCC $ \break_foo -> do callCC $ \continue_foo -> MIO $ wrapState (getMIO $ val body) statefulRetCont $ \inbod -> InLoop (toLoop $ break_foo ()) (toLoop $ continue_foo ()) (getReturn inbod) incr for_r -- | @'break''@ exists the current loop. break' :: MIO TyInLoop r () break' = do InLoop b _ _ <- getState b -- | 'continue'' continues the current loop, passing over -- any control flow that is defined. continue' :: MIO TyInLoop r () continue' = do InLoop _ c _ <- getState c runWithRet :: MIO TyInFunc r a-> RetCont r -> RCont r a runWithRet m r = fmap fst $ runStateT (getMIO m) $ InFunction r -- | 'defer'' executes the given action (or value) before the -- function returns. defer' :: HasValue r valt TyInFunc => valt a -> MIO i r () defer' m = MIO $ do c <- get put $ case c of InLoop a b r -> InLoop a b $ \i -> runWithRet (val m) r >> r i InFunction r -> InFunction $ \i -> runWithRet (val m) r >> r i -- | @'runImperative'@ takes an MIO action as returned by a function, -- and lifts it into IO. runImperative :: MIO TyInFunc a a -> IO a runImperative foo = runContT (callCC $ \ret -> fst <$> runStateT (getMIO foo) (InFunction ret)) return -- | @'function' foo@ takes an ImperativeMonad action and removes it from it's -- specific function context, specifically making it applicable -- in the body of other functions. function :: MIO TyInFunc a a -> MIO i b a function = MIO . liftIO . runImperative instance Eq a => Eq (V TyVal r a) where (Lit a) == (Lit a') = a == a' instance Show a => Show (V TyVal r a) where show (Lit a) = show a instance Num a => Num (V TyVal r a) where (Lit a) + (Lit b) = Lit $ a + b (Lit a) * (Lit b) = Lit $ a * b abs (Lit a) = Lit $ abs a signum (Lit a) = Lit $ signum a fromInteger = Lit . fromInteger instance IsString s => IsString (V TyVal r s) where fromString = Lit . fromString -- | @('&')a@ gets a reference/pointer to the variable specified (&) :: V TyVar r a -> V TyVar s a (&) (R a) = R a -- | 'auto' should just be used where the -- type can be automatically infered and we don't need an initial value -- Use caution, as it is simply an alternate name for 'undefined' auto = undefined -- | 'new' constructs a new reference to the specified pure value new :: (HasValue r (V TyVar r) i) => a -> MIO i r (V TyVar r a) new a = do r <- MIO $ liftIO $ newIORef a return $ R r infixr 0 =: -- | @variable '=:' value@ executes @value@ and writes it -- to the location pointed to by @variable@ (=:) :: (HasValue r valt i, HasValue r (V TyVar r) i) => V TyVar r a -> valt a -> MIO i r () (R ar) =: br = MIO $ do b <- getMIO $ val br liftIO $ writeIORef ar b -- | @'while''(check)@ acts like its imperative @while@ counterpart. while' :: (HasValue r (V b r) i, HasValue r (V b r) TyInLoop, HasValue r valt TyInLoop, CState i) => V b r Bool -> valt () -> MIO i r () while' check = for'(return (), check, return () ) -- | @'if''(check) act@ only performs @act@ if @check@ evaluates to true -- it is specifically a value in its argument. if' :: (HasValue r (V b r) i, HasValue r valt i) => V b r Bool -> valt () -> MIO i r () if' b m = do v <- val b when v (val m) -- | @'modifyOp'@ makes a modification assignment operator -- out of a binary haskell function. -- The suggested use is to replicate functionality of assignments -- like @-=@ or @%=@ from C style languages. modifyOp :: (HasValue r (V TyVar r) i, HasValue r (V k r) i) => (a->b->a) -> V TyVar r a -> V k r b -> MIO i r () modifyOp op (R ar) br = MIO $ do b <- getMIO $ val br liftIO $ modifyIORef ar (\v -> op v b) wrapState :: Monad m => StateT s m a -> (s' -> s -> s') -> (s' -> s) -> StateT s' m a wrapState st fOut fIn = do sp <- get (a, s) <- lift $ runStateT st $ fIn sp put $ fOut sp s return a -- | @'io' action@ takes a haskell 'IO' @action@ and makes it useable from within -- the MIO monad. io :: IO a -> MIO i r a io = liftIO