2-- Module : Streamly.Internal.Data.Array.Stream.Fold.Foreign
3-- Copyright : (c) 2021 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9-- Fold a stream of foreign arrays. @Fold m a b@ in this module works
10-- on a stream of "Array a" and produces an output of type @b@.
12-- Though @Fold m a b@ in this module works on a stream of @Array a@ it is
13-- different from @Data.Fold m (Array a) b@. While the latter works on arrays
14-- as a whole treating them as atomic elements, the folds in this module can
15-- work on the stream of arrays as if it is an element stream with all the
16-- arrays coalesced together. This module allows adapting the element stream
17-- folds in Data.Fold to correctly work on an array stream as if it is an
18-- element stream. For example:
20-- >>> import qualified Streamly.Data.Fold as Fold
21-- >>> import qualified Streamly.Internal.Data.Array.Stream.Foreign as ArrayStream
22-- >>> import qualified Streamly.Internal.Data.Array.Stream.Fold.Foreign as ArrayFold
23-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (arraysOf)
24-- >>> import qualified Streamly.Prelude as Stream
26-- >>> ArrayStream.foldArr (ArrayFold.fromFold (Fold.take 7 Fold.toList)) $ Stream.arraysOf 5 $ Stream.fromList "hello world"
29module Streamly.Internal.Data.Array.Stream.Fold.Foreign
54import Control.Applicative (liftA2)
55import Control.Exception (assert)
56import Control.Monad.Catch (MonadThrow)
57import Control.Monad.IO.Class (MonadIO(..))
58import Foreign.Ptr (minusPtr, plusPtr)
59import Foreign.Storable (Storable(..))
60import GHC.Types (SPEC(..))
61import Streamly.Internal.Data.Array.Foreign.Mut.Type (touch)
62import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
63import Streamly.Internal.Data.Parser.ParserD (Initial(..), Step(..))
64import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
66import qualified Streamly.Internal.Data.Array.Foreign as Array
67import qualified Streamly.Internal.Data.Fold as Fold
68import qualified Streamly.Internal.Data.Parser.ParserD as ParserD
69import qualified Streamly.Internal.Data.Parser.ParserD.Type as ParserD
71import Prelude hiding (concatMap, take)
73-- | Array stream fold.
75-- An array stream fold is basically an array stream "Parser" that does not
76-- fail. In case of array stream folds the count in 'Partial', 'Continue' and
77-- 'Done' is a count of elements that includes the leftover element count in
78-- the array that is currently being processed by the parser. If none of the
79-- elements is consumed by the parser the count is at least the whole array
80-- length. If the whole array is consumed by the parser then the count will be
85newtype Fold m a b = Fold (ParserD.Parser m (Array a) b)
87-------------------------------------------------------------------------------
88-- Constructing array stream folds from element folds and parsers
89-------------------------------------------------------------------------------
91-- | Convert an element 'Fold' into an array stream fold.
94{-# INLINE fromFold #-}
95fromFold :: forall m a b. (MonadIO m, Storable a) =>
96 Fold.Fold m a b -> Fold m a b
97fromFold (Fold.Fold fstep finitial fextract) =
98 Fold (ParserD.Parser step initial fextract)
106 Fold.Partial s1 -> IPartial s1
107 Fold.Done b -> IDone b
109 step s (Array contents start end) = do
114 goArray !_ !cur !fs | cur >= end = do
115 assert (cur == end) (return ())
116 liftIO $ touch contents
117 return $ Partial 0 fs
118 goArray !_ !cur !fs = do
119 x <- liftIO $ peek cur
121 let elemSize = sizeOf (undefined :: a)
122 next = cur `plusPtr` elemSize
125 return $ Done ((end `minusPtr` next) `div` elemSize) b
127 goArray SPEC next fs1
129-- | Convert an element 'Parser' into an array stream fold. If the parser fails
130-- the fold would throw an exception.
133{-# INLINE fromParser #-}
134fromParser :: forall m a b. (MonadIO m, Storable a) =>
135 ParserD.Parser m a b -> Fold m a b
136fromParser (ParserD.Parser step1 initial1 extract1) =
137 Fold (ParserD.Parser step initial1 extract1)
141 step s (Array contents start end) = do
143 then return $ Continue 0 s
144 else goArray SPEC start s
148 {-# INLINE partial #-}
149 partial arrRem cur next elemSize st n fs1 = do
150 let next1 = next `plusPtr` negate (n * elemSize)
151 if next1 >= start && cur < end
152 then goArray SPEC next1 fs1
153 else return $ st (arrRem + n) fs1
155 goArray !_ !cur !fs = do
156 x <- liftIO $ peek cur
157 liftIO $ touch contents
159 let elemSize = sizeOf (undefined :: a)
160 next = cur `plusPtr` elemSize
161 arrRem = (end `minusPtr` next) `div` elemSize
163 ParserD.Done n b -> do
164 return $ Done (arrRem + n) b
165 ParserD.Partial n fs1 ->
166 partial arrRem cur next elemSize Partial n fs1
167 ParserD.Continue n fs1 -> do
168 partial arrRem cur next elemSize Continue n fs1
169 Error err -> return $ Error err
171-- | Adapt an array stream fold.
174{-# INLINE fromArrayFold #-}
175fromArrayFold :: forall m a b. (MonadIO m) =>
176 Fold.Fold m (Array a) b -> Fold m a b
177fromArrayFold f = Fold $ ParserD.fromFold f
179-------------------------------------------------------------------------------
181-------------------------------------------------------------------------------
183-- | Maps a function over the result of fold.
186instance Functor m => Functor (Fold m a) where
188 fmap f (Fold p) = Fold $ fmap f p
190-- | Map a monadic function on the output of a fold.
194rmapM :: Monad m => (b -> m c) -> Fold m a b -> Fold m a c
195rmapM f (Fold p) = Fold $ ParserD.rmapM f p
197-------------------------------------------------------------------------------
198-- Sequential applicative
199-------------------------------------------------------------------------------
201-- | A fold that always yields a pure value without consuming any input.
205{-# INLINE fromPure #-}
206fromPure :: Monad m => b -> Fold m a b
207fromPure = Fold . ParserD.fromPure
209-- | A fold that always yields the result of an effectful action without
210-- consuming any input.
214{-# INLINE fromEffect #-}
215fromEffect :: Monad m => m b -> Fold m a b
216fromEffect = Fold . ParserD.fromEffect
218-- | Applies two folds sequentially on the input stream and combines their
219-- results using the supplied function.
222{-# INLINE serial_ #-}
223serial_ :: MonadThrow m => Fold m x a -> Fold m x b -> Fold m x b
224serial_ (Fold p1) (Fold p2) = Fold $ ParserD.noErrorUnsafeSplit_ p1 p2
226-- | Applies two folds sequentially on the input stream and combines their
227-- results using the supplied function.
230{-# INLINE serialWith #-}
231serialWith :: MonadThrow m
232 => (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c
233serialWith f (Fold p1) (Fold p2) =
234 Fold $ ParserD.noErrorUnsafeSplitWith f p1 p2
236-- | 'Applicative' form of 'serialWith'.
237-- > (<*>) = serialWith id
238instance MonadThrow m => Applicative (Fold m a) where
243 (<*>) = serialWith id
248#if MIN_VERSION_base(4,10,0)
249 {-# INLINE liftA2 #-}
250 liftA2 f x = (<*>) (fmap f x)
253-------------------------------------------------------------------------------
255-------------------------------------------------------------------------------
257-- | Applies a fold on the input stream, generates the next fold from the
258-- output of the previously applied fold and then applies that fold.
262{-# INLINE concatMap #-}
263concatMap :: MonadThrow m =>
264 (b -> Fold m a c) -> Fold m a b -> Fold m a c
265concatMap func (Fold p) =
266 Fold $ ParserD.noErrorUnsafeConcatMap (\x -> let Fold y = func x in y) p
268-- | Monad instance applies folds sequentially. Next fold can depend on the
269-- output of the previous fold. See 'concatMap'.
271-- > (>>=) = flip concatMap
272instance MonadThrow m => Monad (Fold m a) where
273 {-# INLINE return #-}
277 (>>=) = flip concatMap
282-------------------------------------------------------------------------------
283-- Array to Array folds
284-------------------------------------------------------------------------------
287take :: forall m a b. (Monad m, Storable a) => Int -> Fold m a b -> Fold m a b
288take n (Fold (ParserD.Parser step1 initial1 extract1)) =
289 Fold $ ParserD.Parser step initial extract
298 then return $ IPartial $ Tuple' n s
299 else IDone <$> extract1 s
300 IDone b -> return $ IDone b
301 IError err -> return $ IError err
303 {-# INLINE partial #-}
307 then return $ st j (Tuple' i2 s)
308 else Done 0 <$> extract1 s -- i2 == i1 == j == 0
310 step (Tuple' i r) arr = do
311 let len = Array.length arr
317 Partial j s -> partial i1 Partial j s
318 Continue j s -> partial i1 Continue j s
319 Done j b -> return $ Done j b
320 Error err -> return $ Error err
322 let !(Array contents start _) = arr
323 sz = sizeOf (undefined :: a)
324 end = start `plusPtr` (i * sz)
325 arr1 = Array contents start end
326 remaining = negate i1
329 Partial 0 s -> Done remaining <$> extract1 s
330 Partial j s -> return $ Partial (remaining + j) (Tuple' j s)
331 Continue 0 s -> Done remaining <$> extract1 s
332 Continue j s -> return $ Continue (remaining + j) (Tuple' j s)
333 Done j b -> return $ Done (remaining + j) b
334 Error err -> return $ Error err
336 extract (Tuple' _ r) = extract1 r
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list