packages feed

drinkery (empty) → 0

raw patch · 11 files changed

+789/−0 lines, 11 filesdep +basedep +criteriondep +drinkerysetup-changed

Dependencies added: base, criterion, drinkery, mtl, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Fumiaki Kinoshita (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Fumiaki Kinoshita nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,89 @@+# drinkery++drinkery is a yet another stream processing library themed on liquors. While it+offers a simple interface, it also tries to be as expressive as possible.++## Producers++drinkery supports three types of producers: `Barman`, `Sommelier`, and `Tap`.++`Barman r s` is a monad transformer to produce a stream of type `s`. It is good+at interactively serving values.+`topup :: s -> Barman r s m a` is the primary action.+A barman can also accept requests from the downstream using `accept`.++`Sommelier r` is a list-like backtracking monad (also known as ListT done right).+It is useful for sampling elements of containers with effects.+`taste :: Foldable f => f s -> Sommelier r m s` samples elements in any `Foldable`+container. `inquire` to interact with the downstream.++`Tap` is an endless producer. This can be connected to a 'Patron' or a 'Distiller'.++`Barman` and `Sommelier` are converted to `Tap`+by `runBarman` and `runSommelier` respectively.++## Consumer++`Patron r s` is a monad transformer which consumes `s` and may request `r`.++`MonadDrink` provides the actions of `Patron`:++* `drink :: m s` Get one element.+* `spit :: s -> m ()` Leave one element.+* `call :: r -> m ()` Send a request.++`(+&) :: (Monoid r, CloseRequest r, Monad m) => Tap m r s -> Patron r s m a -> m a`+connects a tap with a patron.++## Transducer++`Distiller p q m r s` is a stream transducer which++* Sends `p`+* Consumes `q`+* Receives `r`+* Produces `s`++It is actually a `Tap` where the underlying monad is `Patron`.++There are three composition operators:++* `$&` Distiller-patron+* `$$$` Distiler-distiller+* `++$` Tap-distiller++`+`, `&`, and `$` means a tap, a patron, and a distiller respectively. The middle+characters of these operators signify the resulting structures.++## Why drinkery?++drinkery is designed to be fully featured and complements other libraries' missing+functionalities.++### pipes++`pipes` is quite similar in that both `Proxy` and `Distiller` are bidirectional.+Still there are some differences:++* `Distiller` does not terminate.+* Unlike pipes' `>->`, `$$$` propagates inner requests:+    * `($$$) :: Monoid r => Distiller p q m r s -> Distiller r s m t u -> Distiller p q m t u`+    * `(>->) :: Proxy a' a () b m r	-> Proxy () b c' c m r -> Proxy a' a c' c m r`+* `Patron`, the consumer monad, may leave unconsumed inputs.+* `drinkery` has much fewer operators.++### conduit++Both `drinkery` and `conduit` support leftovers, closing operation, and end of stream.+The main difference is interactivity.++### machines++`machines` has multi-channel consumers but `drinkery` doesn't.+`machines` does not support leftovers, nor interactive producers.++### iteratee++`iteratee` has an ability to handle requests but those are untyped (`SomeException`).+`drinkery` provides a more robust interface for handling requests.+Two monadic producers - `Barman` and `Sommelier` - are easier to use.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ benchmarks/benchmark.hs view
@@ -0,0 +1,23 @@+import qualified Data.Drinkery as D
+import qualified Data.Drinkery.Glass as D
+import Data.Functor.Identity
+import Control.Arrow
+import Control.Monad
+import Criterion.Main
+import Data.List
+import Data.Void
+
+drainD :: D.Distiller () (Maybe Int) IO () (Maybe a) -> IO ()
+drainD h = sourceD D.+& h D.$& D.sinkNull
+
+value :: Int
+value = 10000
+
+sourceD :: (Monoid r, Monad m) => D.Tap m r (Maybe Int)
+sourceD = D.runSommelier $ D.taste [1..value]
+
+main = defaultMain
+  [ bgroup "scan"
+      [ bench "drinkery" $ whnfIO $ drainD (D.scanningMaybe (+) 0)
+      ]
+  ]
+ drinkery.cabal view
@@ -0,0 +1,39 @@+name:                drinkery+version:             0+synopsis:            Boozy streaming library+description:         Boozy streaming library+homepage:            https://github.com/fumieval/drinkery#readme+license:             BSD3+license-file:        LICENSE+author:              Fumiaki Kinoshita+maintainer:          fumiexcel@gmail.com+copyright:           Copyright (c) 2017 Fumiaki Kinoshita+category:            Data+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:+    Data.Drinkery+    Data.Drinkery.Class+    Data.Drinkery.Boozer+    Data.Drinkery.Distiller+    Data.Drinkery.Glass+    Data.Drinkery.Tap+  build-depends:       base >= 4.7 && < 5, transformers, mtl+  ghc-options: -Wall+  default-language:    Haskell2010++benchmark benchmark+  type:           exitcode-stdio-1.0+  main-is:        benchmark.hs+  ghc-options:    -O2+  hs-source-dirs: benchmarks+  build-depends: base, drinkery, criterion+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/fumieval/drinkery
+ src/Data/Drinkery.hs view
@@ -0,0 +1,11 @@+module Data.Drinkery+    ( module Data.Drinkery.Class+    , module Data.Drinkery.Distiller+    , module Data.Drinkery.Tap+    , Patron(..)+    )where++import Data.Drinkery.Boozer+import Data.Drinkery.Class+import Data.Drinkery.Distiller+import Data.Drinkery.Tap
+ src/Data/Drinkery/Boozer.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE Rank2Types, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}+{-# LANGUAGE DeriveFunctor #-}+-----------------------------------------------------------------------+--+-- Module      :  Data.Drinkery.Boozer+-- Copyright   :  (c) Fumiaki Kinoshita 2017+-- License     :  BSD3+--+-- Maintainer  :  Fumiaki Kinoshita <fumiexcel@gmail.com>+--+-- Basic consumer+-----------------------------------------------------------------------+module Data.Drinkery.Boozer where++import Control.Monad.Trans.Class+import Control.Monad.IO.Class+import Control.Monad+import Control.Monad.Reader.Class+import Control.Monad.State.Class+import Data.Drinkery.Class++-- | Boozer is the initial encoding of a consumer.+data Boozer r s m a = Drink (s -> Boozer r s m a)+  | Spit s (Boozer r s m a)+  | Call r (Boozer r s m a)+  | Lift (m (Boozer r s m a))+  | Pure a+  deriving Functor++-- | Tear down a 'Boozer', maintaining a stack of leftovers.+iterBoozer :: ([s] -> a -> z) -- ^ return+  -> ((s -> z) -> z) -- ^ drink+  -> (r -> z -> z) -- ^ call+  -> (forall x. m x -> (x -> z) -> z) -- ^ bind+  -> Boozer r s m a -> z+iterBoozer p d c t = go [] where+  go [] (Drink k) = d (go [] . k)+  go (x : xs) (Drink k) = go xs (k x)+  go xs (Spit s k) = go (s : xs) k+  go xs (Call r k) = c r (go xs k)+  go xs (Lift m) = t m (go xs)+  go xs (Pure a) = p xs a++hoistBoozer :: Functor n => (forall x. m x -> n x) -> Boozer r s m a -> Boozer r s n a+hoistBoozer t = go where+  go (Pure a) = Pure a+  go (Lift m) = Lift $ go <$> t m+  go (Call r k) = Call r (go k)+  go (Spit s k) = Spit s (go k)+  go (Drink f) = Drink $ go . f++instance Functor m => Applicative (Boozer r s m) where+  pure = Pure+  {-# INLINE pure #-}+  (<*>) = ap+  (*>) = (>>)++instance Functor m => Monad (Boozer r s m) where+  return = Pure+  {-# INLINE return #-}+  m0 >>= k = go m0 where+    go (Pure a) = k a+    go (Drink m) = Drink $ go . m+    go (Lift m) = Lift $ fmap go m+    go (Call r c) = Call r (go c)+    go (Spit s c) = Spit s (go c)++  m0 >> k = go m0 where+    go (Pure _) = k+    go (Drink m) = Drink $ go . m+    go (Lift m) = Lift $ fmap go m+    go (Call r c) = Call r (go c)+    go (Spit s c) = Spit s (go c)++instance MonadTrans (Boozer r s) where+  lift m = Lift $ Pure <$> m++instance MonadIO m => MonadIO (Boozer r s m) where+  liftIO m = Lift $ Pure <$> liftIO m++instance MonadReader x m => MonadReader x (Boozer r s m) where+  ask = lift ask+  local f = hoistBoozer (local f)++instance MonadState x m => MonadState x (Boozer r s m) where+  get = lift get+  put = lift . put+  state = lift . state++instance Functor m => MonadDrunk r s (Boozer r s m) where+  drink = Drink Pure+  spit s = Spit s (Pure ())+  call r = Call r (Pure ())++-- | 'Patron' is a CPS'd 'Boozer'.+newtype Patron r s m a = Patron+  { unPatron :: forall x. (a -> Boozer r s m x) -> Boozer r s m x }++runPatron :: Patron r s m a -> Boozer r s m a+runPatron m = unPatron m Pure+{-# INLINE runPatron #-}++instance Functor (Patron r s m) where+  fmap f m = Patron $ \cont -> unPatron m $ cont . f+  {-# INLINE fmap #-}++instance Applicative (Patron r s m) where+  pure a = Patron $ \cont -> cont a+  {-# INLINE pure #-}+  (<*>) = ap+  {-# INLINE (<*>) #-}+  (*>) = (>>)+  {-# INLINE (*>) #-}++instance Monad (Patron r s m) where+  return a = Patron ($a)+  Patron m >>= k = Patron $ \cont -> m (\a -> unPatron (k a) cont)++instance MonadTrans (Patron r s) where+  lift m = Patron $ \cont -> Lift $ cont <$> m++instance MonadDrunk r s (Patron r s m) where+  drink = Patron $ \cont -> Drink cont+  spit s = Patron $ \cont -> Spit s $ cont ()+  call r = Patron $ \cont -> Call r $ cont ()++instance MonadReader x m => MonadReader x (Patron r s m) where+  ask = lift ask+  local f m = Patron $ \cont -> unPatron m (local f . cont)++instance MonadState x m => MonadState x (Patron r s m) where+  get = lift get+  put = lift . put+  state = lift . state++instance MonadIO m => MonadIO (Patron r s m) where+  liftIO m = Patron $ \cont -> Lift $ cont <$> liftIO m
+ src/Data/Drinkery/Class.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE Rank2Types, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}+{-# LANGUAGE DeriveFunctor #-}+-----------------------------------------------------------------------+--+-- Module      :  Data.Drinkery.Class+-- Copyright   :  (c) Fumiaki Kinoshita 2017+-- License     :  BSD3+--+-- Maintainer  :  Fumiaki Kinoshita <fumiexcel@gmail.com>+--+-- Basic classes+-----------------------------------------------------------------------+module Data.Drinkery.Class where++import Control.Monad.Trans.Class+import Control.Monad.Trans.Cont+import Control.Monad.Trans.Maybe+import qualified Control.Monad.Trans.Reader as Reader+import qualified Control.Monad.Trans.State.Lazy as Lazy+import qualified Control.Monad.Trans.State.Strict as Strict+import qualified Control.Monad.Trans.Writer.Lazy as Lazy+import qualified Control.Monad.Trans.Writer.Strict as Strict+import qualified Control.Monad.Trans.RWS.Lazy as Lazy+import qualified Control.Monad.Trans.RWS.Strict as Strict++class Monad m => MonadDrunk r s m | m -> r s where+  drink :: m s+  spit :: s -> m ()+  call :: r -> m ()++instance MonadDrunk r s m => MonadDrunk r s (Reader.ReaderT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance MonadDrunk r s m => MonadDrunk r s (Lazy.StateT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance MonadDrunk r s m => MonadDrunk r s (Strict.StateT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance (Monoid x, MonadDrunk r s m) => MonadDrunk r s (Lazy.WriterT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance (Monoid x, MonadDrunk r s m) => MonadDrunk r s (Strict.WriterT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance (Monoid y, MonadDrunk r s m) => MonadDrunk r s (Lazy.RWST x y z m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance (Monoid y, MonadDrunk r s m) => MonadDrunk r s (Strict.RWST x y z m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance MonadDrunk r s m => MonadDrunk r s (MaybeT m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++instance MonadDrunk r s m => MonadDrunk r s (ContT x m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++-- | Get one element without consuming.+smell :: MonadDrunk r s m => m s+smell = do+  s <- drink+  spit s+  return s++class CloseRequest a where+  -- | A value representing a close request+  closeRequest :: a++instance CloseRequest () where+  closeRequest = ()++instance CloseRequest a => CloseRequest [a] where+  closeRequest = [closeRequest]
+ src/Data/Drinkery/Distiller.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE Rank2Types, BangPatterns, LambdaCase, FlexibleContexts #-}+-----------------------------------------------------------------------+--+-- Module      :  Data.Drinkery.Distiller+-- Copyright   :  (c) Fumiaki Kinoshita 2017+-- License     :  BSD3+--+-- Maintainer  :  Fumiaki Kinoshita <fumiexcel@gmail.com>+--+-- Stream transducers+-----------------------------------------------------------------------+module Data.Drinkery.Distiller+  ( Distiller+  -- * Special combinators+  , (+&)+  , ($&)+  -- * Attaching a distiller+  , (++$)+  , ($$$)+  -- * Patron+  , (++&)+  , ($$&)+  -- * Stock distillers+  , mapping+  , traversing+  , filtering+  , scanning+  , scanningMaybe+  , throughMaybe+  -- * Internal+  , boozeOn+  , distillWith+  ) where++import Control.Monad.Trans.Class+import Data.Drinkery.Tap+import Data.Drinkery.Class+import Data.Drinkery.Boozer++boozeOn :: (Monoid r, Monad m) => (forall x. n x -> m x)+  -> (r -> [s] -> Tap m r s -> a -> m z)+  -> r -> [s] -> Tap m r s+  -> Boozer r s n a+  -> m z+boozeOn t cont = go where+  go !r [] (Tap f) (Drink k) = f r >>= \(s, b) -> go mempty [] b (k s)+  go !r (s : ss) b (Drink f) = go r ss b (f s)+  go !r ss b (Spit s k) = go r (s : ss) b k+  go !r ss b (Call r' k) = go (mappend r r') ss b k+  go !r ss b (Lift m) = t m >>= go r ss b+  go !r ss b (Pure a) = cont r ss b a+{-# INLINE boozeOn #-}++-- | 'Distiller p q m r s' is a stream transducer which has five parameters:+--+-- * @p@ request to the upstream+-- * @q@ input+-- * @m@ underlying monad+-- * @r@ request from the downstream+-- * @s@ output+type Distiller p q m = Tap (Patron p q m)++distillWith :: (Monoid p, Monad m) => (forall x. n x -> m x) -> Tap m p q -> Distiller p q n r s -> Tap m r s+distillWith trans = go mempty [] where+  go r lo b (Tap m) = Tap $ \rs -> boozeOn trans+    (\r' lo' b' (s, cont) -> pure (s, go r' lo' b' cont))+    r lo b (runPatron (m rs))+{-# INLINE distillWith #-}++infix 6 +&+infixr 7 $&+infix 6 ++&+infixl 7 ++$+infixr 7 $$&+infixl 8 $$$++-- | Connect a tap with a patron.+--+-- Mnemonic:+--+-- * @+@ Left operand is a tap.+-- * @+@ Returns a tap (along with the result).+-- * @&@ Right operand is a patron.+(++&) :: (Monoid r, Monad m) => Tap m r s -> Patron r s m a -> m (Tap m r s, a)+t ++& p = boozeOn id (\r s t' a -> pure (orderTap r $ foldr consTap t' s, a)) mempty [] t (runPatron p)+{-# INLINE (++&) #-}++-- | Attach a distiller to a tap.+--+-- Mnemonic:+--+-- * @+@ Left operand is a tap.+-- * @+@ Returns a tap.+-- * @$@ Right operand is a distiller.+(++$) :: (Monoid p, Monad m) => Tap m p q -> Distiller p q m r s -> Tap m r s+(++$) = distillWith id+{-# INLINE (++$) #-}++-- | Full duplex composition of distillers.+--+-- Mnemonic:+--+-- * @$@ Left operand is a distiller.+-- * @$@ Returns a distiller.+-- * @$@ Right operand is a distiller.+($$$) :: (Monoid r, Monad m) => Distiller p q m r s -> Distiller r s m t u -> Distiller p q m t u+($$$) = distillWith lift+{-# INLINE ($$$) #-}++-- | Attach a distiller to a patron.+--+-- Mnemonic:+--+-- * @$@ Left operand is a distiller.+-- * @$@ Returns the used distiller.+-- * @&@ Right operand is a patron.+($$&) :: (Monoid r, Monad m) => Distiller p q m r s -> Patron r s m a+  -> Patron p q m (Distiller p q m r s, a)+d $$& b = boozeOn lift (\r s t a -> pure (orderTap r $ foldr consTap t s, a)) mempty [] d (runPatron b)+{-# INLINE ($$&) #-}++-- | Connect a tap with a patron and close the used tap.+(+&) :: (Monoid r, CloseRequest r, Monad m) => Tap m r s -> Patron r s m a -> m a+t +& b = do+  (t', a) <- t ++& b+  _ <- unTap t' closeRequest+  return a+{-# INLINE (+&) #-}++-- | Like '$&&' but discards the used distiller.+($&) :: (Monoid r, Monad m) => Distiller p q m r s -> Patron r s m a -> Patron p q m a+t $& b = fmap snd $ t $$& b+{-# INLINE ($&) #-}++-- | Create a request-preserving distiller.+propagating :: Functor m => Patron r a m (b, Distiller r a m r b) -> Distiller r a m r b+propagating m = Tap $ \r -> call r >> m+{-# INLINE propagating #-}++mapping :: Functor m => (a -> b) -> Distiller r a m r b+mapping f = propagating $ drink >>= \a -> return (f a, mapping f)++traversing :: Monad m => (a -> m b) -> Distiller r a m r b+traversing f = propagating $ drink >>= \a -> lift (f a) >>= \b -> return (b, traversing f)++filtering :: (Monoid r, Functor m) => (a -> Bool) -> Distiller r a m r a+filtering f = propagating $ drink >>= \a -> if f a+  then return (a, filtering f)+  else unTap (filtering f) mempty++scanning :: (Monoid r, Functor m) => (b -> a -> b) -> b -> Distiller r a m r b+scanning f b0 = consTap b0 $ go b0 where+  go b = propagating $ fmap (\a -> let !b' = f b a in (b', go $ b')) drink+{-# INLINE scanning #-}++scanningMaybe :: (Monoid r, Functor m) => (b -> a -> b) -> b -> Distiller r (Maybe a) m r (Maybe b)+scanningMaybe f b0 = consTap (Just b0) $ go b0 where+  go b = Tap $ \r -> Patron $ \cont -> Call r $ Drink $ \case+    Just a -> let !b' = f b a in cont (Just b', go b')+    Nothing -> cont (Nothing, go b)+{-# INLINE scanningMaybe #-}++-- | Transform a 'Distiller' to operate on a stream of 'Maybe's.+throughMaybe :: (Monoid r, Monad m) => Distiller p q m r s -> Distiller p (Maybe q) m r (Maybe s)+throughMaybe d = Tap $ \rs -> iterBoozer+  (\qs (s, d') -> (Just s, throughMaybe d') <$ mapM_ (spit . Just) qs)+  (\cont -> drink >>= maybe (pure end) cont)+  (\p cont -> call p >> cont)+  ((>>=) . lift)+  $ runPatron $ unTap d rs+  where+    end = (Nothing, throughMaybe d)
+ src/Data/Drinkery/Glass.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE LambdaCase #-}+-----------------------------------------------------------------------+--+-- Module      :  Data.Drinkery.Glass+-- Copyright   :  (c) Fumiaki Kinoshita 2017+-- License     :  BSD3+--+-- Maintainer  :  Fumiaki Kinoshita <fumiexcel@gmail.com>+--+-- Things to work with finite streams+-----------------------------------------------------------------------+module Data.Drinkery.Glass+  ( eof+  , runBarman+  , runSommelier+  , pour+  -- * 'MonadDrunk' actions+  , foldl'+  , foldM+  , traverse_+  , sinkNull+  )+where++import Control.Applicative+import Control.Monad hiding (foldM)+import Data.Drinkery.Class+import Data.Drinkery.Tap+import qualified Data.Foldable as F++-- | End of stream+eof :: (Applicative m, Alternative f) => Tap m r (f a)+eof = Tap $ const $ pure (empty, eof)++-- | Run a 'Barman' action and terminate the stream with 'eof'.+runBarman :: (Monoid r, Applicative m, Alternative f) => Barman r (f s) m a -> Tap m r (f s)+runBarman m = unBarman m (const eof)+{-# INLINE runBarman #-}++-- | Run 'Sommelier' and terminate the stream with 'eof'.+runSommelier :: (Monoid r, Applicative m, Alternative f) => Sommelier r m s -> Tap m r (f s)+runSommelier m = unSommelier m (consTap . pure) eof+{-# INLINE runSommelier #-}++pour :: (Monoid r, Applicative f, Applicative m) => s -> Barman r (f s) m ()+pour = topup . pure++foldl' :: (Foldable t, MonadDrunk r (t a) m) => (b -> a -> b) -> b -> m b+foldl' f = go where+  go b = do+    t <- drink+    if null t+      then return b+      else go $! F.foldl' f b t++foldM :: (Foldable t, MonadDrunk r (t a) m) => (b -> a -> m b) -> b -> m b+foldM f = go where+  go b = do+    t <- drink+    if null t+      then return b+      else F.foldlM f b t >>= go++traverse_ :: (Foldable t, MonadDrunk r (t a) m) => (a -> m b) -> m ()+traverse_ f = go where+  go = do+    t <- drink+    if null t then return () else F.traverse_ f t >> go++sinkNull :: (Foldable t, MonadDrunk r (t a) m) => m ()+sinkNull = drink >>= \t -> unless (null t) sinkNull
+ src/Data/Drinkery/Tap.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE Rank2Types, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}+-----------------------------------------------------------------------+--+-- Module      :  Data.Drinkery.Tap+-- Copyright   :  (c) Fumiaki Kinoshita 2017+-- License     :  BSD3+--+-- Maintainer  :  Fumiaki Kinoshita <fumiexcel@gmail.com>+--+-- Stream producers+-----------------------------------------------------------------------+module Data.Drinkery.Tap (+  Tap(..)+  , consTap+  , orderTap+  , makeTap+  , Barman(..)+  , topup+  , accept+  , inexhaustible+  , Sommelier(..)+  , taste+  , inquire+) where++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Class+import Data.Drinkery.Class++-- | @'Tap' m r s@ is a non-monadic, endless producer of @s@. It takes a request+-- @r@.+newtype Tap m r s = Tap { unTap :: r -> m (s, Tap m r s) }++-- | Prepend a new element, delaying requests.+consTap :: (Monoid r, Applicative m) => s -> Tap m r s -> Tap m r s+consTap s t = Tap $ \r -> pure (s, Tap $ unTap t . mappend r)+{-# INLINE consTap #-}++-- | Send a request to a 'Tap'.+orderTap :: (Monoid r) => r -> Tap m r s -> Tap m r s+orderTap r t = Tap $ \r' -> unTap t $! mappend r r'+{-# INLINE orderTap #-}++-- | Involve an action.+makeTap :: (Monoid r, Monad m) => m (Tap m r s) -> Tap m r s+makeTap m = Tap $ \r -> m >>= \t -> unTap t r+{-# INLINE makeTap #-}++-- | Monadic producer+newtype Barman r s m a = Barman { unBarman :: (a -> Tap m r s) -> Tap m r s }++instance Functor (Barman r s m) where+  fmap = liftM++instance Applicative (Barman r s m) where+  pure = return+  (<*>) = ap++instance Monad (Barman r s m) where+  return a = Barman ($ a)+  Barman m >>= k = Barman $ \cont -> m $ \a -> unBarman (k a) cont++instance MonadTrans (Barman r s) where+  lift m = Barman $ \k -> Tap $ \rs -> m >>= \a -> unTap (k a) rs++instance MonadDrunk r s m => MonadDrunk r s (Barman p q m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++-- | Produce one element. Orders are put off.+topup :: (Monoid r, Applicative m) => s -> Barman r s m ()+topup s = Barman $ \cont -> consTap s (cont ())++-- | Accept orders and clear the queue.+accept :: Monoid r => Barman r s m r+accept = Barman $ \cont -> Tap $ \rs -> unTap (cont rs) mempty++-- | Create a infinite 'Tap' from a 'Barman'.+--+-- @Barman r s (Boozer p q m) x -> Distiller p q m r s@+--+inexhaustible :: Barman r s m x -> Tap m r s+inexhaustible t = unBarman t $ const $ inexhaustible t++-- | Backtracking producer a.k.a. "ListT done right".+newtype Sommelier r m s = Sommelier+  { unSommelier :: forall x. (s -> Tap m r x -> Tap m r x) -> Tap m r x -> Tap m r x }++instance Functor (Sommelier r m) where+  fmap f m = Sommelier $ \c e -> unSommelier m (c . f) e++instance Applicative (Sommelier r m) where+  pure = return+  (<*>) = ap++instance Monad (Sommelier r m) where+  return s = Sommelier $ \c e -> c s e+  m >>= k = Sommelier $ \c e -> unSommelier m (\s -> unSommelier (k s) c) e++instance Alternative (Sommelier r m) where+  empty = Sommelier $ \_ e -> e+  a <|> b = Sommelier $ \c e -> unSommelier a c (unSommelier b c e)++instance MonadTrans (Sommelier r) where+  lift m = Sommelier $ \c e -> Tap $ \rs -> m >>= \a -> unTap (c a e) rs++instance MonadIO m => MonadIO (Sommelier r m) where+  liftIO m = Sommelier $ \c e -> Tap $ \rs -> liftIO m >>= \a -> unTap (c a e) rs++instance MonadDrunk r s m => MonadDrunk r s (Sommelier p m) where+  drink = lift drink+  spit = lift . spit+  call = lift . call++-- | Take all the elements in a 'Foldable' container.+taste :: Foldable f => f s -> Sommelier r m s+taste xs = Sommelier $ \c e -> foldr c e xs++-- | Get a request.+inquire :: Monoid r => Sommelier r m r+inquire = Sommelier $ \c e -> Tap $ \rs -> unTap (c rs e) mempty