-- Hybrid ReaderT/StateT style over STT -- 100M Ints in ~4 seconds over IO -- Seems to optimize away the inner monad pretty well when feasible {-# LANGUAGE TypeApplications, RankNTypes, MultiParamTypeClasses, GeneralizedNewtypeDeriving, DeriveFunctor, FlexibleInstances, FlexibleContexts, ScopedTypeVariables, StandaloneDeriving, UnboxedTuples #-} module Control.Monad.Trans.Push where import Control.Monad.ST.Trans (runST, STRef, readSTRef, writeSTRef, newSTRef) import Control.Monad.ST.Trans.Internal (STT(STT), STTRet(STTRet)) import GHC.ST (ST(ST)) import qualified Data.Vector as V import qualified Data.Vector.Storable as VS import qualified Data.Vector.Unboxed as VU import qualified Data.Vector.Generic as VG import qualified Data.Vector.Generic.Mutable as VGM import Control.Monad.Push.Class (MonadPush, push) -- | The internal return type of a push action. -- The Int value is the new vector used length. data Res a = Res !Int !a deriving Functor -- | A monad transformer that lets you push things onto a stack. -- This is probably super unsafe; see the docs for Control.Monad.ST.Trans. newtype PushT v p m a = PushT (forall s . Int -> (STRef s (v s p)) -> (STT s m (Res a))) deriving instance Functor m => Functor (PushT v p m) instance Monad m => Applicative (PushT v p m) where {-# INLINE pure #-} pure a = PushT $ \u _ -> (return (Res u a)) {-# INLINE (<*>) #-} (PushT f) <*> (PushT g) = PushT $ \u v -> f u v >>= (\(Res u' o1) -> g u' v >>= (\(Res u'' o2) -> return (Res u'' (o1 o2)))) instance Monad m => Monad (PushT v p m) where {-# INLINE return #-} return = pure {-# INLINE (>>=) #-} PushT g >>= f = PushT $ \u v -> ((g u v) >>= (\(Res u' x) -> let PushT h = f x in h u' v)) {-# INLINE (>>) #-} PushT g >> PushT h = PushT $ \u v -> g u v >>= (\(Res u' _) -> h u' v) -- | This seems *highly* questionable, but appears to get the job done. {-# INLINE liftST #-} liftST :: Applicative m => ST s a -> STT s m a liftST (ST f) = STT (\s -> let (# s', a #) = f s in pure (STTRet s' a)) instance (Monad m, VGM.MVector v p) => MonadPush p (PushT v p m) where {-# INLINE push #-} push a = PushT $ \used vec' -> do vec <- readSTRef vec' if (VGM.length vec == used) then do bigger <- liftST $ VGM.grow vec used -- Double the length liftST $ VGM.write bigger used a writeSTRef vec' bigger else do liftST $ VGM.write vec used a return $ Res (used+1) () -- | Run the monad transformer. {-# INLINE runPushT #-} runPushT :: (Monad m, VG.Vector v p) => PushT (VG.Mutable v) p m a -> m (a, v p) runPushT (PushT action) = runST $ do initial <- liftST $ VGM.new 1 vecRef <- newSTRef initial (Res used out) <- action 0 vecRef final <- readSTRef vecRef vec <- liftST $ VG.freeze (VGM.slice 0 used final) return (out, vec) -- | Specialized to Unboxed vectors. {-# INLINE runPushTU #-} runPushTU :: forall p a m . (VU.Unbox p, Monad m) => PushT (VU.MVector) p m a -> m (a, VU.Vector p) runPushTU = runPushT -- | Specialized to standard Boxed vectors. {-# INLINE runPushTB #-} runPushTB :: forall p a m . Monad m => PushT (V.MVector) p m a -> m (a, V.Vector p) -- | Specialized to Storable vectors. runPushTB = runPushT {-# INLINE runPushTS #-} runPushTS :: forall p a m . (VS.Storable p, Monad m) => PushT (VS.MVector) p m a -> m (a, VS.Vector p) runPushTS = runPushT