packages feed

stc-lang-1.0.0: src/Control/Monad/Generator.hs

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE CPP #-}

module Control.Monad.Generator
    ( IsGenerator(..)
    , liftIO
    , foldlGenerator
    , foldlGeneratorT
    , foldlGenerator_
    , foldlGeneratorT_
    , chanToGenerator
    , ioReaderToGenerator
    , foldableGenerator
    , foldableGenerator'
    , foldableGenerator''
    , foldableGeneratorEval
    , listGenerator
    , stateToGenerator
    , Generator
    -- ** A mutable IO generator variable
    , GenVar, newGenVar, pull
    ) where

import Control.Applicative
import Control.Arrow
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Control.DeepSeq
import Control.Monad.State
import Data.Foldable (foldr')
import Data.Tuple
import qualified GHC.Exts (IsList(..))


-- | A natural transformation
type f ~> g = forall x. f x -> g x

------------------------------------------------------------------
--
-- The generator
--
------------------------------------------------------------------
-- | There are three basic ways to construct this generator, which
-- correspond to the (not exposed) constructors of this
-- type. 1. 'finish' this marks the end of a generator, 2. 'yield' is
-- used to return a value and continue with the computation afterwards
-- and finally using 'liftIO' you can execute any IO action and then
-- continue.
data Generator m a
    = Finished
    | Yield (Generator m a)
            a
    | NeedM (m (Generator m a))

-- At first I implemented all of the `Applicative` and `Monad`
-- functions fully, that is to say with all the recursion down the
-- source generator.  I was already suspecting a pattern there,
-- because the recursion for both looked very similar, I just couldn't
-- quite figure it out.  When suddenly something *magical* happened.
-- I was looking at `Alternative`, because that instance is needed for
-- `MonadComprehensions` and I was again reminded of just how similar
-- `Alternative` is to `Monoid` and I realized that `Generator` is in
-- fact a `Monoid`. The empty element is `Finished` because it can be
-- appended or prepended to any generator without changing its meaning
-- and `mappend` is simply exhausting the first generator first,
-- followed by the second one.  After having that realization
-- implementing `Monad` and `Applicative` became really easy because
-- you just simply create a new generator by applying the function and
-- then prepend a recursion of the respective operation (`>>=` or
-- `<*>`)
-- You can see the generator in action by running the examples at the bottom in ghci with `runGenerator`
mappendGen :: Functor m => Generator m a -> Generator m a -> Generator m a
Finished `mappendGen` gen2 = gen2
NeedM sc `mappendGen` gen2 = NeedM $ (`mappendGen` gen2) <$> sc
Yield g v `mappendGen` gen2 = Yield (g `mappendGen` gen2) v

instance Functor m => Monoid (Generator m a) where
    mempty = Finished
#if MIN_VERSION_base(4,11,0)
instance Functor m => Semigroup (Generator m a) where
    (<>) = mappendGen
#else
    mappend = mappendGen
#endif
instance Functor m => Functor (Generator m) where
    fmap _ Finished = Finished
    fmap f (NeedM m) = NeedM $ fmap (fmap f) m
    fmap f (Yield g a) = Yield (fmap f g) (f a)

instance Functor m => Applicative (Generator m) where
    pure = Yield Finished
    Finished <*> _ = Finished
    NeedM m <*> v = NeedM $ (<*> v) <$> m
    Yield fg f <*> v = fmap f v `mappend` (fg <*> v)

instance Functor m => Monad (Generator m) where
    return = pure
    Finished >>= _ = Finished
    NeedM m >>= f = NeedM $ (>>= f) <$> m
    Yield cont a >>= f = f a `mappend` (cont >>= f)

-- | This is needed to get the Monad comprehensions
instance Functor m => Alternative (Generator m) where
    empty = mempty
    (<|>) = mappend

-- | IO can be embedded easily
instance MonadIO m => MonadIO (Generator m) where
    liftIO = needM . liftIO

instance Monad m => GHC.Exts.IsList (Generator m a) where
    type Item (Generator m a) = a
    fromList = listGenerator
    toList _ = error "toList: need monad to evaluate generator"

foldlGeneratorT ::
       (IsGenerator g f, Monad m)
    => (f ~> m)
    -> (b -> a -> m b)
    -> b
    -> g a
    -> m b
foldlGeneratorT trans ac = flip go
  where
    go gen seed' =
        trans (step gen) >>=
        maybe (pure seed') (\(a, gen') -> go gen' =<< ac seed' a)

foldlGenerator ::
       (IsGenerator g m, Monad m) => (b -> a -> m b) -> b -> g a -> m b
foldlGenerator = foldlGeneratorT id

foldlGeneratorT_ ::
       (IsGenerator g f, Monad m) => (f ~> m) -> (a -> m ()) -> g a -> m ()
foldlGeneratorT_ trans f = foldlGeneratorT trans (\() a -> f a) ()

foldlGenerator_ :: (IsGenerator g m, Monad m) => (a -> m ()) -> g a -> m ()
foldlGenerator_ = foldlGeneratorT_ id

ioReaderToGenerator :: (IsGenerator g m, Monad g) => m (Maybe a) -> g a
ioReaderToGenerator reader = recur
  where
    recur = maybe finish (`yield` recur) =<< needM reader

chanToGenerator ::
       (MonadIO m, IsGenerator g m, Monad g) => Chan (Maybe a) -> g a
chanToGenerator = ioReaderToGenerator . liftIO . readChan

foldableGenerator :: (Foldable f, IsGenerator g m) => f a -> g a
foldableGenerator = foldr' yield finish

foldableGeneratorEval ::
       (Foldable f, IsGenerator g m) => (forall b. a -> b -> b) -> f a -> g a
foldableGeneratorEval eval = foldr (\a rest -> a `eval` yield a rest) finish

foldableGenerator' :: (Foldable f, IsGenerator g m) => f a -> g a
foldableGenerator' = foldableGeneratorEval seq

foldableGenerator'' :: (Foldable f, IsGenerator g m, NFData a) => f a -> g a
foldableGenerator'' = foldableGeneratorEval deepseq

-----------------------------------------------------------------
--
-- Creating generators
--
------------------------------------------------------------------
listGenerator :: IsGenerator g m => [a] -> g a
listGenerator = foldableGenerator

-- | A generator crated with this will run until it returns `Nothing` in which case the generator finishes
stateToGenerator ::
       (Monad g, IsGenerator g m) => StateT s m (Maybe a) -> s -> g a
stateToGenerator st s = do
    (a, s') <- needM $ runStateT st s
    maybe finish (`yield` stateToGenerator st s') a

------------------------------------------------------------------
--
-- Some more fun stuff that can be done with them
--
------------------------------------------------------------------
-- One fun thing we can do in IO is put the generator in a mutable variable and then just pull values from that.
type GenVar a = MVar (Generator IO a)

newGenVar :: Generator IO a -> IO (GenVar a)
newGenVar = newMVar

-- | This pulls a new value from this var (if possible) and updates its state
pull :: GenVar a -> IO (Maybe a)
pull =
    flip modifyMVar $
    fmap (maybe (Finished, Nothing) (second Just . swap)) . step

------------------------------------------------------------------
--
-- Some examples of comprehensions, composition and state embedding
--
------------------------------------------------------------------
permutations :: Generator IO (Int, Char)
permutations =
    [(i, c) | i <- listGenerator [0 .. 9], c <- listGenerator ['a' .. 'f']]

nonReflexivePermutations :: Int -> Generator IO (Int, Int)
nonReflexivePermutations i = [(a, b) | a <- ints, b <- ints, a /= b]
  where
    ints = listGenerator [0 .. i]

justSomeStuffWithInts :: Generator IO Int
justSomeStuffWithInts =
    flip stateToGenerator 0 $ do
        s <- get
        if s < 100
            then do
                modify (+ 4)
                pure $ Just s
            else do
                liftIO $ putStrLn "We have reached 100" -- It can do IO as well ;)
                pure Nothing

-- and they are all compatible and can be joined together (and depend on each other)
-- Probably dont run this ... it creates a **lot** of output
crazy :: Generator IO (Int, Int, Char)
crazy =
    [ (a + b, a * d, c)
    | i <- justSomeStuffWithInts
    , (b, d) <- nonReflexivePermutations i
    , (a, c) <- permutations
    ]

------------------------------------------------------------------
--
-- A generator interface
--
------------------------------------------------------------------
-- Something I thought of this morning.
-- There could also be a generic interface for generators
-- | A generator @g@ that runs in the monad @m@
class IsGenerator g m | g -> m where
    yield :: a -> g a -> g a
    finish :: g a
    needM :: m a -> g a
    isFinished :: g a -> Bool
    default isFinished :: Eq (g a) =>
        g a -> Bool
    isFinished = (== finish)
    -- | Run until the generator yields its first value or finishes.
    -- Returns the created value and a new generator which represents its updated internal state.
    step :: g a -> m (Maybe (a, g a))
    -- | Run a generator producing a list of output values
    toList :: g a -> m [a]
    default toList :: Monad m =>
        g a -> m [a]
    toList = foldlGenerator (\b a -> pure $ a : b) []

instance Monad m => IsGenerator (Generator m) m where
    yield = flip Yield
    finish = Finished
    needM = NeedM . fmap pure
    isFinished Finished = True
    isFinished _ = False
    step Finished = pure Nothing
    step (NeedM ac) = ac >>= step
    step (Yield g a) = pure $ Just (a, g)
    toList Finished = pure []
    toList (NeedM ac) = ac >>= toList
    toList (Yield g a) = (a :) <$> toList g