Permalink
Fetching contributors…
Cannot retrieve contributors at this time
184 lines (140 sloc) 5.2 KB
{-# LANGUAGE RankNTypes, CPP #-}
{-# OPTIONS_GHC -fno-warn-missing-methods #-}
{-| You can think of `Shell` as @[]@ + `IO` + `Managed`. In fact, you can embed
all three of them within a `Shell`:
> select :: [a] -> Shell a
> liftIO :: IO a -> Shell a
> using :: Managed a -> Shell a
Those three embeddings obey these laws:
> do { x <- select m; select (f x) } = select (do { x <- m; f x })
> do { x <- liftIO m; liftIO (f x) } = liftIO (do { x <- m; f x })
> do { x <- with m; using (f x) } = using (do { x <- m; f x })
>
> select (return x) = return x
> liftIO (return x) = return x
> using (return x) = return x
... and `select` obeys these additional laws:
> select xs <|> select ys = select (xs <|> ys)
> select empty = empty
You typically won't build `Shell`s using the `Shell` constructor. Instead,
use these functions to generate primitive `Shell`s:
* `empty`, to create a `Shell` that outputs nothing
* `return`, to create a `Shell` that outputs a single value
* `select`, to range over a list of values within a `Shell`
* `liftIO`, to embed an `IO` action within a `Shell`
* `using`, to acquire a `Managed` resource within a `Shell`
Then use these classes to combine those primitive `Shell`s into larger
`Shell`s:
* `Alternative`, to concatenate `Shell` outputs using (`<|>`)
* `Monad`, to build `Shell` comprehensions using @do@ notation
If you still insist on building your own `Shell` from scratch, then the
`Shell` you build must satisfy this law:
> -- For every shell `s`:
> _foldIO s (FoldM step begin done) = do
> x <- begin
> x' <- _foldIO s (FoldM step (return x) return)
> done x'
... which is a fancy way of saying that your `Shell` must call @\'begin\'@
exactly once when it begins and call @\'done\'@ exactly once when it ends.
-}
module Turtle.Shell (
-- * Shell
Shell(..)
, foldIO
, fold
, sh
, view
-- * Embeddings
, select
, liftIO
, using
) where
import Control.Applicative
import Control.Monad (MonadPlus(..), ap)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Managed (MonadManaged(..), with)
#if MIN_VERSION_base(4,9,0)
import qualified Control.Monad.Fail as Fail
#endif
import Control.Foldl (Fold(..), FoldM(..))
import qualified Control.Foldl as Foldl
import Data.Foldable (Foldable)
import qualified Data.Foldable
import Data.Monoid
import Data.String (IsString(..))
import Prelude -- Fix redundant import warnings
-- | A @(Shell a)@ is a protected stream of @a@'s with side effects
newtype Shell a = Shell { _foldIO :: forall r . FoldM IO a r -> IO r }
-- | Use a @`FoldM` `IO`@ to reduce the stream of @a@'s produced by a `Shell`
foldIO :: MonadIO io => Shell a -> FoldM IO a r -> io r
foldIO s f = liftIO (_foldIO s f)
-- | Use a `Fold` to reduce the stream of @a@'s produced by a `Shell`
fold :: MonadIO io => Shell a -> Fold a b -> io b
fold s f = foldIO s (Foldl.generalize f)
-- | Run a `Shell` to completion, discarding any unused values
sh :: MonadIO io => Shell a -> io ()
sh s = fold s (pure ())
-- | Run a `Shell` to completion, `print`ing any unused values
view :: (MonadIO io, Show a) => Shell a -> io ()
view s = sh (do
x <- s
liftIO (print x) )
instance Functor Shell where
fmap f s = Shell (\(FoldM step begin done) ->
let step' x a = step x (f a)
in _foldIO s (FoldM step' begin done) )
instance Applicative Shell where
pure = return
(<*>) = ap
instance Monad Shell where
return a = Shell (\(FoldM step begin done) -> do
x <- begin
x' <- step x a
done x' )
m >>= f = Shell (\(FoldM step0 begin0 done0) -> do
let step1 x a = _foldIO (f a) (FoldM step0 (return x) return)
_foldIO m (FoldM step1 begin0 done0) )
fail _ = mzero
instance Alternative Shell where
empty = Shell (\(FoldM _ begin done) -> do
x <- begin
done x )
s1 <|> s2 = Shell (\(FoldM step begin done) -> do
x <- _foldIO s1 (FoldM step begin return)
_foldIO s2 (FoldM step (return x) done) )
instance MonadPlus Shell where
mzero = empty
mplus = (<|>)
instance MonadIO Shell where
liftIO io = Shell (\(FoldM step begin done) -> do
x <- begin
a <- io
x' <- step x a
done x' )
instance MonadManaged Shell where
using resource = Shell (\(FoldM step begin done) -> do
x <- begin
x' <- with resource (step x)
done x' )
#if MIN_VERSION_base(4,9,0)
instance Fail.MonadFail Shell where
fail = Prelude.fail
#endif
instance Monoid a => Monoid (Shell a) where
mempty = pure mempty
mappend = liftA2 mappend
-- | Shell forms a semiring, this is the closest approximation
instance Monoid a => Num (Shell a) where
fromInteger n = select (replicate (fromInteger n) mempty)
(+) = (<|>)
(*) = (<>)
instance IsString a => IsString (Shell a) where
fromString str = pure (fromString str)
-- | Convert a list to a `Shell` that emits each element of the list
select :: Foldable f => f a -> Shell a
select as = Shell (\(FoldM step begin done) -> do
x0 <- begin
let step' a k x = do
x' <- step x a
k $! x'
Data.Foldable.foldr step' done as $! x0 )