on master help sign in
Breakdown to Source, SourceM and BSource
commita4da54b7626261b1f2fc0a770a9befa197baf690 authorMichael <michael@snoyman.com> dateSat 10 Dec 2011, 19:23 (15 years ago) parent1f5d6ed
6 files changed+266−88
conduit/Data/Conduit.hs+23−71 conduit/Data/Conduit/Binary.hs+7−7 conduit/Data/Conduit/List.hs+6−5 conduit/Data/Conduit/Types/Source.hs+138 conduit/Data/Conduit/Util/Source.hs+84 conduit/test/main.hs+8−5
conduit/Data/Conduit.hs+23−71
@@ -5,20 +5,20 @@
55{-# LANGUAGE DeriveDataTypeable #-}
66module Data.Conduit
77 ( -- * Helper types
8− Stream (..)
9− , SinkResult (..)
8+ SinkResult (..)
109 , ConduitResult (..)
1110 -- * Main types
12− , Source (..)
1311 , Sink (..)
1412 , SinkInside (..)
1513 , Conduit
1614 -- * Connect pieces together
1715 , ($$)
16+ , (<$$>)
1817 , ($=)
1918 , (=$)
20− -- * Helper functions
21− , mkSource
19+ -- * Other modules
20+ , module Data.Conduit.Types.Source
21+ , module Data.Conduit.Util.Source
2222 ) where
2323
2424import Control.Monad.Trans.Resource
@@ -30,13 +30,8 @@ import qualified Data.IORef as I
3030import Data.Typeable (Typeable)
3131import Control.Monad (liftM)
3232import Control.Applicative (Applicative (..))
33−import Data.Monoid (Monoid (mempty, mappend, mconcat))
34−
35−data Stream a = EOF | Chunks [a]
36− deriving Show
37−instance Functor Stream where
38− fmap _ EOF = EOF
39− fmap f (Chunks l) = Chunks (map f l)
33+import Data.Conduit.Types.Source
34+import Data.Conduit.Util.Source
4035
4136data SinkResult input output = SinkResult [input] (Maybe output)
4237instance Functor (SinkResult input) where
@@ -44,38 +39,6 @@ instance Functor (SinkResult input) where
4439
4540data ConduitResult input output = ConduitResult [input] (Stream output)
4641
47−data SourceInside m a = SourceInside
48− { sourcePull :: ResourceT m (Stream a)
49− , sourcePush :: [a] -> ResourceT m ()
50− }
51−newtype Source m a = Source { unSource :: ResourceT m (SourceInside m a) }
52−instance MonadBaseControl IO m => Monoid (Source m a) where
53− mempty = mkSource $ return EOF
54− mappend a b = mconcat [a, b]
55− mconcat [] = mempty
56− mconcat (Source mnext:rest0) = Source $ do
57− next0 <- mnext
58− istate <- liftBase $ I.newIORef $ Just (next0, rest0)
59− unSource $ mkSource $ go istate
60− where
61− go istate = do
62− state <- liftBase $ I.readIORef istate
63− case state of
64− Nothing -> return EOF
65− Just (next, rest) -> do
66− stream <- sourcePull next
67− case stream of
68− EOF ->
69− case rest of
70− Source a:as -> do
71− a' <- a
72− liftBase $ I.writeIORef istate $ Just (a', as)
73− go istate
74− [] -> do
75− liftBase $ I.writeIORef istate Nothing
76− return EOF
77− Chunks _ -> return stream
78−
7942type SinkInsideData a m b = Stream a -> ResourceT m (SinkResult a b)
8043data SinkInside a m b =
8144 SinkData (SinkInsideData a m b)
@@ -172,44 +135,31 @@ sinkJoin (Sink msinkI) = Sink $ do
172135
173136type Conduit a m b = Stream a -> ResourceT m (ConduitResult a b)
174137
175−mkSource :: MonadBaseControl IO m
176− => ResourceT m (Stream a)
177− -> Source m a
178−mkSource pull = Source $ do
179− buffer <- liftBase $ I.newIORef []
180− return SourceInside
181− { sourcePull = do
182− x <- liftBase $ I.atomicModifyIORef buffer (\a -> ([], a))
183− if null x
184− then pull
185− else return $ Chunks x
186− , sourcePush = \s ->
187− if null s
188− then return ()
189− else liftBase $
190− I.atomicModifyIORef buffer (\a -> (s ++ a, ()))
191− }
192−
193138infixr 0 $$
194139
195140($$) :: MonadBaseControl IO m => Source m a -> Sink a m b -> ResourceT m b
196−Source msource $$ Sink msink = do
141+source $$ Sink msink = do
197142 sinkI <- msink
198143 case sinkI of
199144 SinkNoData output -> return output
200145 SinkData sink -> do
201− source <- msource
202− connect' source sink
146+ bs <- bsource source
147+ connect' bs sink
203148 where
204− connect' source sink = do
205− stream <- sourcePull source
149+ connect' bs sink = do
150+ stream <- bsourcePull bs
206151 SinkResult leftover mres <- sink stream
207− sourcePush source leftover
152+ bsourceUnpull bs leftover
208153 case (stream, mres) of
209154 (_, Just res) -> return res
210− (Chunks _, Nothing) -> connect' source sink
155+ (Chunks _, Nothing) -> connect' bs sink
211156 (EOF, Nothing) -> liftBase (throwIO NothingAfterEOF)
212157
158+infixr 0 <$$>
159+
160+(<$$>) :: MonadBaseControl IO m => SourceM m a -> Sink a m b -> ResourceT m b
161+SourceM msrc <$$> sink = msrc >>= ($$ sink)
162+
213163data NothingAfterEOF = NothingAfterEOF
214164 deriving (Show, Typeable)
215165instance Exception NothingAfterEOF
@@ -220,13 +170,15 @@ infixl 1 $=
220170 => Source m a
221171 -> Conduit a m b
222172 -> Source m b
223−Source msource $= pipe = Source $ do
224− source <- msource
173+_ $= _ =
174+ undefined
175+ {-
225176 source' <- unSource $ mkSource $ do
226177 ConduitResult leftover result <- sourcePull source >>= pipe
227178 sourcePush source leftover
228179 return result
229180 return source'
181+ -}
230182
231183infixr 0 =$
232184
conduit/Data/Conduit/Binary.hs+7−7
@@ -16,15 +16,15 @@ import Control.Monad.Trans.Resource (with, release)
1616
1717sourceFile :: MonadBaseControl IO m
1818 => FilePath
19− -> Source m S.ByteString
20−sourceFile fp = Source $ do
21− (key, handle) <- with (liftBase $ openFile fp ReadMode) (liftBase . hClose)
22− unSource $ mkSource $ do
19+ -> SourceM m S.ByteString
20+sourceFile fp = sourceM
21+ (with (liftBase $ openFile fp ReadMode) (liftBase . hClose))
22+ (\(key, _) -> release key)
23+ (\(_, handle) -> do
2324 bs <- liftBase $ S.hGetSome handle 50
24− -- FIXME protection against being called after close?
2525 if S.null bs
26− then release key >> return EOF
27− else return $ Chunks [bs]
26+ then return EOF
27+ else return $ Chunks [bs])
2828
2929sinkFile :: MonadBaseControl IO m
3030 => FilePath
conduit/Data/Conduit/List.hs+6−5
@@ -32,12 +32,13 @@ fold f accum0 = Sink $ do
3232 \accum -> (foldl' f accum cs, ())
3333 return $ SinkResult [] Nothing
3434
35−fromList :: MonadBaseControl IO m => [a] -> Source m a
36−fromList l = Source $ do
37− il <- liftBase $ I.newIORef l
38− unSource $ mkSource $ do
35+fromList :: MonadBaseControl IO m => [a] -> SourceM m a
36+fromList l = sourceM
37+ (liftBase (I.newIORef l))
38+ (const (return ()))
39+ (\il -> do
3940 l' <- liftBase $ I.atomicModifyIORef il $ \x -> ([], x)
40− return $ if null l' then EOF else Chunks l'
41+ return $ if null l' then EOF else Chunks l')
4142
4243take :: MonadBaseControl IO m
4344 => Int
conduit/Data/Conduit/Types/Source.hsnew file+138
@@ -0,0 +1,138 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+-- | Defines the types for a source, which is a producer of data.
4+module Data.Conduit.Types.Source
5+ ( Stream (..)
6+ , Source (..)
7+ , SourceM (..)
8+ , BSource (..)
9+ , SourceInvariantException (..)
10+ ) where
11+
12+import Control.Monad.Trans.Resource (ResourceT)
13+import Data.Monoid (Monoid (..))
14+import Control.Monad.Base (MonadBase (liftBase))
15+import qualified Data.IORef as I
16+import Control.Monad (liftM)
17+import Data.Typeable (Typeable)
18+import Control.Exception (Exception, throw)
19+
20+-- | In general, a source represents a producer of a stream of data. A source
21+-- can either produce output, or indicate that there is no more data. This type
22+-- represents the output from a 'Source'.
23+data Stream a = EOF -- ^ no more data
24+ | Chunks [a] -- ^ produced some data
25+
26+instance Functor Stream where
27+ fmap _ EOF = EOF
28+ fmap f (Chunks a) = Chunks (map f a)
29+
30+-- | A 'Source' has two operations on it: pull some data, and close the
31+-- 'Source'. Since 'Source' is built on top of 'ResourceT', all acquired
32+-- resources should be automatically released anyway. Closing a 'Source' early
33+-- is merely an optimization to free scarce resources as soon as possible.
34+--
35+-- A 'Source' has two invariants:
36+--
37+-- * It is illegal to call 'sourcePull' after a previous call returns 'EOF'.
38+--
39+-- * It is legal to call 'sourceClose' multiple times.
40+--
41+-- 'Source's are not expected to call 'sourceClose' when they return an 'EOF'
42+-- from 'sourcePull', though based on the second invariant, they are free to do
43+-- so.
44+data Source m a = Source
45+ { sourcePull :: ResourceT m (Stream a)
46+ , sourceClose :: ResourceT m ()
47+ }
48+
49+instance Monad m => Functor (Source m) where
50+ fmap f src = src
51+ { sourcePull = liftM (fmap f) (sourcePull src)
52+ }
53+
54+-- | All but the simplest of 'Source's (e.g., @repeat@ and @cycle@) require
55+-- some type of state to track their current status. This may be in the form of
56+-- a mutable variable (e.g., @IORef@), or via opening a resource like a
57+-- @Handle@. While a 'Source' is given no opportunity to acquire such
58+-- resources, this type is.
59+--
60+-- A 'SourceM' is simply a monadic action that returns a 'Source'. One nice
61+-- consequence of this is the possibility of creating an efficient 'Monoid'
62+-- instance, which will only acquire one resource at a time, instead of bulk
63+-- acquiring all resources at the beginning of running the 'SourceM'.
64+--
65+-- Note that each time you \"call\" a @SourceM@, it is started from scratch. If
66+-- you want a resumable source (e.g., one which can be passed to multiple
67+-- @Sink@s), you likely want to use a 'BSource'.
68+newtype SourceM m a = SourceM { genSource :: ResourceT m (Source m a) }
69+
70+instance Monad m => Functor (SourceM m) where
71+ fmap f (SourceM msrc) = SourceM (liftM (fmap f) msrc)
72+
73+instance MonadBase IO m => Monoid (SourceM m a) where
74+ mempty = SourceM (return Source
75+ { sourcePull = return EOF
76+ , sourceClose = return ()
77+ })
78+ mappend a b = mconcat [a, b]
79+ mconcat [] = mempty
80+ mconcat (SourceM mnext:rest0) = SourceM $ do
81+ -- open up the first SourceM...
82+ next0 <- mnext
83+ -- and place it in a mutable reference along with all of the upcoming
84+ -- SourceMs
85+ istate <- liftBase (I.newIORef (next0, rest0))
86+ return Source
87+ { sourcePull = pull istate
88+ , sourceClose = close istate
89+ }
90+ where
91+ pull istate =
92+ liftBase (I.readIORef istate) >>= pull'
93+ where
94+ pull' (current, rest) = do
95+ stream <- sourcePull current
96+ case stream of
97+ -- end of the current Source
98+ EOF -> do
99+ -- close the current Source
100+ sourceClose current
101+ case rest of
102+ -- ... and open the next one
103+ SourceM ma:as -> do
104+ a <- ma
105+ liftBase (I.writeIORef istate (a, as))
106+ -- continue pulling base on this new state
107+ pull istate
108+ -- no more source, return an EOF
109+ [] -> do
110+ -- give an error message if the first Source
111+ -- invariant is violated (read data after EOF)
112+ liftBase (I.writeIORef istate (throw (PullAfterEOF "SourceM:mconcat")))
113+ return EOF
114+ Chunks _ -> return stream
115+ close istate = do
116+ -- we only need to close the current Source, since they are opened
117+ -- one at a time
118+ (current, _) <- liftBase (I.readIORef istate)
119+ sourceClose current
120+
121+-- | When actually interacting with 'Source's, we usually want to be able to
122+-- buffer the output, in case any intermediate steps return leftover data. A
123+-- 'BSource' allows for such buffering, via the 'bsourceUnpull' function.
124+--
125+-- A 'BSource', unlike a 'SourceM', is resumable, meaning it can be passed to
126+-- multiple 'Sink's without restarting.
127+--
128+-- Finally, a 'BSource' relaxes one of the invariants of a 'Source': calling
129+-- 'bsourcePull' after an 'EOF' will simply return another 'EOF'.
130+data BSource m a = BSource
131+ { bsourcePull :: ResourceT m (Stream a)
132+ , bsourceUnpull :: [a] -> ResourceT m () -- ^ It is the responsibility of the 'BSource' to check if the argument is null.
133+ , bsourceClose :: ResourceT m ()
134+ }
135+
136+data SourceInvariantException = PullAfterEOF String
137+ deriving (Show, Typeable)
138+instance Exception SourceInvariantException
conduit/Data/Conduit/Util/Source.hsnew file+84
@@ -0,0 +1,84 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+-- | Utilities for constructing and converting 'Source', 'SourceM' and
3+-- 'BSource' types. Please see "Data.Conduit.Types.Source" for more information
4+-- on the base types.
5+module Data.Conduit.Util.Source
6+ ( sourceM
7+ , bsource
8+ , bsourceM
9+ ) where
10+
11+import Control.Monad.Trans.Resource (ResourceT)
12+import Data.Conduit.Types.Source
13+import Control.Monad.Base (MonadBase (liftBase))
14+import qualified Data.IORef as I
15+
16+-- | Construct a 'SourceM'.
17+sourceM :: Monad m
18+ => ResourceT m state -- ^ resource and/or state allocation
19+ -> (state -> ResourceT m ()) -- ^ resource and/or state cleanup
20+ -> (state -> ResourceT m (Stream output)) -- ^ Pull function. Note that this need not explicitly perform any cleanup.
21+ -> SourceM m output
22+sourceM alloc cleanup pull = SourceM $ do
23+ state <- alloc
24+ return Source
25+ { sourcePull = pull state
26+ , sourceClose = cleanup state
27+ }
28+
29+-- | State of a 'BSource'
30+data BState a = EmptyOpen -- ^ nothing in buffer, EOF not received yet
31+ | EmptyClosed -- ^ nothing in buffer, EOF has been received
32+ | Open [a] -- ^ something in buffer, EOF not received yet
33+ | Closed [a] -- ^ something in buffer, EOF has been received
34+ deriving Show
35+
36+-- | Convert a 'SourceM' into a 'BSource'.
37+bsource :: MonadBase IO m
38+ => Source m output
39+ -> ResourceT m (BSource m output)
40+bsource src = do
41+ -- state is a boolean indicating whether EOF was reached, and a buffer
42+ istate <- liftBase (I.newIORef EmptyOpen)
43+ return BSource
44+ { bsourcePull = do
45+ mresult <- liftBase $ I.atomicModifyIORef istate $ \state ->
46+ case state of
47+ Open buffer -> (EmptyOpen, Just (Chunks buffer))
48+ Closed buffer -> (EmptyClosed, Just (Chunks buffer))
49+ EmptyOpen -> (EmptyOpen, Nothing)
50+ EmptyClosed -> (EmptyClosed, Just EOF)
51+ case mresult of
52+ Nothing -> do
53+ result <- sourcePull src
54+ case result of
55+ EOF -> do
56+ liftBase $ I.writeIORef istate EmptyClosed
57+ return EOF
58+ _ -> return result
59+ Just result -> return result
60+ , bsourceUnpull =
61+ \x ->
62+ if null x
63+ then return ()
64+ else liftBase $ I.atomicModifyIORef istate $ \state ->
65+ case state of
66+ Open buffer -> (Open (x ++ buffer), ())
67+ Closed buffer -> (Closed (x ++ buffer), ())
68+ EmptyOpen -> (Open x, ())
69+ EmptyClosed -> (Closed x, ())
70+ , bsourceClose = do
71+ action <- liftBase $ I.atomicModifyIORef istate $ \state ->
72+ case state of
73+ Open x -> (Closed x, sourceClose src)
74+ Closed _ -> (state, return ())
75+ EmptyOpen -> (EmptyClosed, sourceClose src)
76+ EmptyClosed -> (state, return ())
77+ action
78+ }
79+
80+-- | Convert a 'SourceM' into a 'BSource'.
81+bsourceM :: MonadBase IO m
82+ => SourceM m output
83+ -> ResourceT m (BSource m output)
84+bsourceM (SourceM msrc) = msrc >>= bsource
conduit/test/main.hs+8−5
@@ -15,27 +15,30 @@ import qualified Data.ByteString as S
1515main :: IO ()
1616main = hspecX $ do
1717 describe "sum" $ do
18+ it "works for 1..10" $ do
19+ x <- runResourceT $ CL.fromList [1..10] C.<$$> CL.fold (+) (0 :: Int)
20+ x @?= sum [1..10]
1821 prop "is idempotent" $ \list ->
19− (unsafePerformIO $ runResourceT $ CL.fromList list C.$$ CL.fold (+) (0 :: Int))
22+ (unsafePerformIO $ runResourceT $ CL.fromList list C.<$$> CL.fold (+) (0 :: Int))
2023 == sum list
2124 describe "Monoid instance for Source" $ do
2225 it "mappend" $ do
23− x <- runResourceT $ (CL.fromList [1..5 :: Int] `mappend` CL.fromList [6..10]) C.$$ CL.fold (+) 0
26+ x <- runResourceT $ (CL.fromList [1..5 :: Int] `mappend` CL.fromList [6..10]) C.<$$> CL.fold (+) 0
2427 x @?= sum [1..10]
2528 it "mconcat" $ do
2629 x <- runResourceT $ mconcat
2730 [ CL.fromList [1..5 :: Int]
2831 , CL.fromList [6..10]
2932 , CL.fromList [11..20]
30− ] C.$$ CL.fold (+) 0
33+ ] C.<$$> CL.fold (+) 0
3134 x @?= sum [1..20]
3235 describe "file access" $ do
3336 it "read" $ do
3437 bs <- S.readFile "conduit.cabal"
35− bss <- runResourceT $ CB.sourceFile "conduit.cabal" C.$$ CL.consume
38+ bss <- runResourceT $ CB.sourceFile "conduit.cabal" C.<$$> CL.consume
3639 bs @=? S.concat bss
3740 it "write" $ do
38− runResourceT $ CB.sourceFile "conduit.cabal" C.$$ CB.sinkFile "tmp"
41+ runResourceT $ CB.sourceFile "conduit.cabal" C.<$$> CB.sinkFile "tmp"
3942 bs1 <- S.readFile "conduit.cabal"
4043 bs2 <- S.readFile "tmp"
4144 bs1 @=? bs2