1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving#-}
4{-# LANGUAGE MultiParamTypeClasses #-}
5{-# LANGUAGE RankNTypes #-}
6{-# LANGUAGE StandaloneDeriving #-}
7{-# LANGUAGE UndecidableInstances #-} -- XXX
10-- Module : Asyncly.Streams
11-- Copyright : (c) 2017 Harendra Kumar
14-- Maintainer : harendra.kumar@gmail.com
15-- Stability : experimental
21 -- * Product Style Composition
77 -- * Sum Style Composition
89import Control.Applicative (Alternative (..), liftA2)
90import Control.Monad (MonadPlus(..), ap, liftM)
91import Control.Monad.Base (MonadBase (..))
92import Control.Monad.Catch (MonadThrow)
93import Control.Monad.Error.Class (MonadError(..))
94import Control.Monad.IO.Class (MonadIO(..))
95import Control.Monad.Reader.Class (MonadReader(..))
96import Control.Monad.State.Class (MonadState(..))
97import Control.Monad.Trans.Class (MonadTrans)
98import Data.Semigroup (Semigroup(..))
99import Prelude hiding (drop, take, zipWith)
104-- Different styles of streams represented by different types dictate the
105-- manner in which a stream is processed in monadic or applicative composition.
106-- Each type differs in the processing order of the elements or processing
107-- parallelism. The types have the same underlying representation and
108-- therefore can be converted back and forth easily to switch the composition
109-- style at any point.
111-- 'StreamT' and 'InterleavedT' process the streams serially but differ in the
112-- order in which the elements of the streams are processed. 'StreamT' goes
113-- depth first that is it combine one element of the first stream to every
114-- element of the second stream before it comes back to process the second
115-- element of the first stream. 'InterleavedT' takes turns when processing
116-- elements from each stream and therefore all iterations finish at the same
117-- time rather than one iteration at a time as in the case of 'StreamT'.
118-- Depending on the cache behavior or IO behavior 'InterleavedT' might be
119-- slightly more efficient than 'StreamT' or vice-versa.
121-- 'AsyncT' and 'ParallelT' can process the individual elements of the streams
122-- in parallel. 'AsyncT' is the parallel version of 'StreamT', it follows the
123-- order of 'StreamT', processing multiple elements of a stream concurrently.
124-- 'ParallelT' follows the order of 'InterleavedT', processing and interleaving
125-- both streams concurrently. Also, note that 'fmap' on these streams would
126-- also work in a concurrent fashion i.e. multiple elements in a stream or
127-- multiple streams can be mapped concurrently.
129-- Zipping types have a different style of composition altogether, they zip the
130-- streams rather than multiplying them. These types provide only Applicative
131-- composition and no Monad instances. 'ZipStream' zips serially i.e. it
132-- produces one element from each stream in a serial manner and then zips the
133-- two items. 'ZipAsync' on the other hand zips in parallel, it produces one
134-- element from each stream concurrently and then zips the two.
136-- It may help develop a better understanding if we think in terms of stream
137-- generation (production) side parallelism versus stream processing
138-- (consumption) side parallelism. The sum style operators (e.g. '<|' or '<|>')
139-- generate streams in parallel and then combine the results into a unified
140-- stream. On the other hand the product style composition (e.g. 'AsyncT' or
141-- 'ParallelT' or 'ZipAsync') dictate how the elements of a stream are
142-- processed when it is consumed.
144------------------------------------------------------------------------------
145-- Types that can behave as a Stream
146------------------------------------------------------------------------------
148-- | Class of types that can represent a stream of elements of some type 'a' in
150class Streaming t where
151 toStream :: t m a -> Stream m a
152 fromStream :: Stream m a -> t m a
154------------------------------------------------------------------------------
155-- StreamT iterates serially in the Monad and Applicative compositions
156------------------------------------------------------------------------------
158-- | Note that 'StreamT' style composition works well operationally for
159-- infinite streams. However if the second stream is infinite we will never
160-- come back to the second iteration of the first stream.
162newtype StreamT m a = StreamT {getStreamT :: Stream m a}
163 deriving (Semigroup, Monoid, MonadTrans, MonadIO, MonadThrow)
165deriving instance MonadAsync m => Alternative (StreamT m)
166deriving instance MonadAsync m => MonadPlus (StreamT m)
167deriving instance (MonadBase b m, Monad m) => MonadBase b (StreamT m)
168deriving instance MonadError e m => MonadError e (StreamT m)
169deriving instance MonadReader r m => MonadReader r (StreamT m)
170deriving instance MonadState s m => MonadState s (StreamT m)
172instance Streaming StreamT where
173 toStream = getStreamT
176-- XXX The Functor/Applicative/Num instances for all the types are exactly the
177-- same, how can we reduce this boilerplate (use TH)? We cannot derive them
178-- from a single base type because they depend on the Monad instance which is
179-- different for each type.
181------------------------------------------------------------------------------
183------------------------------------------------------------------------------
185-- | Execute a monadic action sequentially for each element in the 'AsyncT'
186-- stream, i.e. an iteration finishes completely before the next one starts.
187instance Monad m => Monad (StreamT m) where
189 (StreamT (Stream m)) >>= f = StreamT $ Stream $ \_ stp yld ->
190 let run x = (runStream x) Nothing stp yld
191 yield a Nothing = run $ getStreamT (f a)
192 yield a (Just r) = run $ getStreamT (f a)
193 <> getStreamT (StreamT r >>= f)
194 in m Nothing stp yield
196------------------------------------------------------------------------------
198------------------------------------------------------------------------------
200instance Monad m => Applicative (StreamT m) where
201 pure a = StreamT $ scons a Nothing
204------------------------------------------------------------------------------
206------------------------------------------------------------------------------
208instance Monad m => Functor (StreamT m) where
211------------------------------------------------------------------------------
213------------------------------------------------------------------------------
215instance (Monad m, Num a) => Num (StreamT m a) where
216 fromInteger n = pure (fromInteger n)
226instance (Monad m, Fractional a) => Fractional (StreamT m a) where
227 fromRational n = pure (fromRational n)
233instance (Monad m, Floating a) => Floating (StreamT m a) where
253 logBase = liftA2 logBase
255------------------------------------------------------------------------------
256-- InterleavedT interleaves the iterations in the Monad and Applicative
258------------------------------------------------------------------------------
260-- | Note that 'InterleavedT' style composition cannot work well operationally
261-- on infinite streams. It will keep accumulating the elements of the stream
262-- resulting in continuous increase in space consumption.
264newtype InterleavedT m a = InterleavedT {getInterleavedT :: Stream m a}
265 deriving (Semigroup, Monoid, MonadTrans, MonadIO, MonadThrow)
267deriving instance MonadAsync m => Alternative (InterleavedT m)
268deriving instance MonadAsync m => MonadPlus (InterleavedT m)
269deriving instance (MonadBase b m, Monad m) => MonadBase b (InterleavedT m)
270deriving instance MonadError e m => MonadError e (InterleavedT m)
271deriving instance MonadReader r m => MonadReader r (InterleavedT m)
272deriving instance MonadState s m => MonadState s (InterleavedT m)
274instance Streaming InterleavedT where
275 toStream = getInterleavedT
276 fromStream = InterleavedT
278-- | Execute a monadic action for each element in the stream, in a fairly
279-- interleaved manner i.e. iterations yield alternately.
280instance Monad m => Monad (InterleavedT m) where
282 (InterleavedT (Stream m)) >>= f = InterleavedT $ Stream $ \_ stp yld ->
283 let run x = (runStream x) Nothing stp yld
284 yield a Nothing = run $ getInterleavedT (f a)
285 yield a (Just r) = run $ getInterleavedT (f a)
287 getInterleavedT (InterleavedT r >>= f)
288 in m Nothing stp yield
290------------------------------------------------------------------------------
292------------------------------------------------------------------------------
294instance Monad m => Applicative (InterleavedT m) where
295 pure a = InterleavedT $ scons a Nothing
298------------------------------------------------------------------------------
300------------------------------------------------------------------------------
302instance Monad m => Functor (InterleavedT m) where
305------------------------------------------------------------------------------
307------------------------------------------------------------------------------
309instance (Monad m, Num a) => Num (InterleavedT m a) where
310 fromInteger n = pure (fromInteger n)
320instance (Monad m, Fractional a) => Fractional (InterleavedT m a) where
321 fromRational n = pure (fromRational n)
327instance (Monad m, Floating a) => Floating (InterleavedT m a) where
347 logBase = liftA2 logBase
349------------------------------------------------------------------------------
350-- AsyncT runs the iterations asynchronously in the Monad and Applicative
351-- compositions. More iterations are started in parallel on demand. It can work
352-- with infinite streams.
353------------------------------------------------------------------------------
355-- | Just like 'StreamT', 'AsyncT' style composition works well operationally
356-- for infinite streams. However, if the second stream is infinite we will
357-- never be able to finish the first stream.
359-- Note that 'fmap' on 'AsyncT' would map on multiple elements concurrently.
361newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}
362 deriving (Semigroup, Monoid, MonadTrans)
364deriving instance MonadAsync m => Alternative (AsyncT m)
365deriving instance MonadAsync m => MonadPlus (AsyncT m)
366deriving instance MonadAsync m => MonadIO (AsyncT m)
367deriving instance MonadAsync m => MonadThrow (AsyncT m)
368deriving instance (MonadBase b m, MonadAsync m) => MonadBase b (AsyncT m)
369deriving instance (MonadError e m, MonadAsync m) => MonadError e (AsyncT m)
370deriving instance (MonadReader r m, MonadAsync m) => MonadReader r (AsyncT m)
371deriving instance (MonadState s m, MonadAsync m) => MonadState s (AsyncT m)
373instance Streaming AsyncT where
377{-# INLINE parbind #-}
379 :: (forall c. Stream m c -> Stream m c -> Stream m c)
383parbind par m f = go m
386 Stream $ \ctx stp yld ->
387 let run x = (runStream x) ctx stp yld
388 yield a Nothing = run $ f a
389 yield a (Just r) = run $ f a `par` (go r)
390 in g Nothing stp yield
392-- | Execute a monadic action for each element in the stream, running
393-- iterations in parallel, but giving preference to iterations started
395instance MonadAsync m => Monad (AsyncT m) where
397 (AsyncT m) >>= f = AsyncT $ parbind par m g
398 where g x = getAsyncT (f x)
399 par = joinStreamVar2 (SVarStyle Conjunction LIFO)
401------------------------------------------------------------------------------
403------------------------------------------------------------------------------
405instance MonadAsync m => Applicative (AsyncT m) where
406 pure a = AsyncT $ scons a Nothing
409------------------------------------------------------------------------------
411------------------------------------------------------------------------------
413instance MonadAsync m => Functor (AsyncT m) where
416------------------------------------------------------------------------------
418------------------------------------------------------------------------------
420instance (MonadAsync m, Num a) => Num (AsyncT m a) where
421 fromInteger n = pure (fromInteger n)
431instance (MonadAsync m, Fractional a) => Fractional (AsyncT m a) where
432 fromRational n = pure (fromRational n)
438instance (MonadAsync m, Floating a) => Floating (AsyncT m a) where
458 logBase = liftA2 logBase
460------------------------------------------------------------------------------
461-- ParallelT runs all iterations in parallel in the Monad and Applicative
462-- compositions. Iterations are scheduled in a round-robin fashion.
463------------------------------------------------------------------------------
465-- | Note that, just like 'InterleavedT', 'ParallelT' style composition cannot
466-- work well operationally on infinite streams. It will keep accumulating the
467-- elements of the stream resulting in continuous increase in space
470-- Note that 'fmap' on 'ParallelT' would map on both streams concurrently.
472newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
473 deriving (Semigroup, Monoid, MonadTrans)
475deriving instance MonadAsync m => Alternative (ParallelT m)
476deriving instance MonadAsync m => MonadPlus (ParallelT m)
477deriving instance MonadAsync m => MonadIO (ParallelT m)
478deriving instance MonadAsync m => MonadThrow (ParallelT m)
479deriving instance (MonadBase b m, MonadAsync m) => MonadBase b (ParallelT m)
480deriving instance (MonadError e m, MonadAsync m) => MonadError e (ParallelT m)
481deriving instance (MonadReader r m, MonadAsync m) => MonadReader r (ParallelT m)
482deriving instance (MonadState s m, MonadAsync m) => MonadState s (ParallelT m)
484instance Streaming ParallelT where
485 toStream = getParallelT
486 fromStream = ParallelT
488-- | Execute a monadic action for each element in the stream, running
489-- iterations in a fairly parallel manner, i.e. all iterations are equally
491instance MonadAsync m => Monad (ParallelT m) where
493 (ParallelT m) >>= f = ParallelT $ parbind par m g
494 where g x = getParallelT (f x)
495 par = joinStreamVar2 (SVarStyle Conjunction FIFO)
497------------------------------------------------------------------------------
499------------------------------------------------------------------------------
501instance MonadAsync m => Applicative (ParallelT m) where
502 pure a = ParallelT $ scons a Nothing
505------------------------------------------------------------------------------
507------------------------------------------------------------------------------
509instance MonadAsync m => Functor (ParallelT m) where
512------------------------------------------------------------------------------
514------------------------------------------------------------------------------
516instance (MonadAsync m, Num a) => Num (ParallelT m a) where
517 fromInteger n = pure (fromInteger n)
527instance (MonadAsync m, Fractional a) => Fractional (ParallelT m a) where
528 fromRational n = pure (fromRational n)
534instance (MonadAsync m, Floating a) => Floating (ParallelT m a) where
554 logBase = liftA2 logBase
556------------------------------------------------------------------------------
557-- Serially Zipping Streams
558------------------------------------------------------------------------------
560-- | Zip two streams serially using a pure zipping function.
561zipWith :: Streaming t => (a -> b -> c) -> t m a -> t m b -> t m c
562zipWith f m1 m2 = fromStream $ go (toStream m1) (toStream m2)
564 go mx my = Stream $ \_ stp yld -> do
566 let yield2 b Nothing = yld (f a b) Nothing
567 yield2 b (Just rb) = yld (f a b) (Just (go ra rb))
568 in (runStream my) Nothing stp yield2
569 let yield1 a Nothing = merge a snil
570 yield1 a (Just ra) = merge a ra
571 (runStream mx) Nothing stp yield1
573-- | 'ZipStream' zips serially i.e. it produces one element from each stream in
574-- a serial manner and then zips the two elements.
575newtype ZipStream m a = ZipStream {getZipStream :: Stream m a}
576 deriving (Semigroup, Monoid)
578deriving instance MonadAsync m => Alternative (ZipStream m)
580instance Monad m => Functor (ZipStream m) where
581 fmap f (ZipStream (Stream m)) = ZipStream $ Stream $ \_ stp yld ->
582 let yield a Nothing = yld (f a) Nothing
583 yield a (Just r) = yld (f a)
584 (Just (getZipStream (fmap f (ZipStream r))))
585 in m Nothing stp yield
587instance Monad m => Applicative (ZipStream m) where
588 pure a = ZipStream $ scons a Nothing
591instance Streaming ZipStream where
592 toStream = getZipStream
593 fromStream = ZipStream
595instance (Monad m, Num a) => Num (ZipStream m a) where
596 fromInteger n = pure (fromInteger n)
606instance (Monad m, Fractional a) => Fractional (ZipStream m a) where
607 fromRational n = pure (fromRational n)
613instance (Monad m, Floating a) => Floating (ZipStream m a) where
633 logBase = liftA2 logBase
635------------------------------------------------------------------------------
636-- Parallely Zipping Streams
637------------------------------------------------------------------------------
639-- | Zip two streams asyncly (i.e. both the elements being zipped are generated
640-- concurrently) using a pure zipping function.
641zipAsyncWith :: (Streaming t, MonadAsync m)
642 => (a -> b -> c) -> t m a -> t m b -> t m c
643zipAsyncWith f m1 m2 = fromStream $ Stream $ \_ stp yld -> do
646 (runStream (toStream (zipWith f ma mb))) Nothing stp yld
648-- | 'ZipAsync' zips in parallel, it produces one element from each stream
649-- concurrently and then zips the two.
650newtype ZipAsync m a = ZipAsync {getZipAsync :: Stream m a}
651 deriving (Semigroup, Monoid)
653deriving instance MonadAsync m => Alternative (ZipAsync m)
655instance Monad m => Functor (ZipAsync m) where
656 fmap f (ZipAsync (Stream m)) = ZipAsync $ Stream $ \_ stp yld ->
657 let yield a Nothing = yld (f a) Nothing
658 yield a (Just r) = yld (f a)
659 (Just (getZipAsync (fmap f (ZipAsync r))))
660 in m Nothing stp yield
662instance MonadAsync m => Applicative (ZipAsync m) where
663 pure a = ZipAsync $ scons a Nothing
664 (<*>) = zipAsyncWith id
666instance Streaming ZipAsync where
667 toStream = getZipAsync
668 fromStream = ZipAsync
670instance (MonadAsync m, Num a) => Num (ZipAsync m a) where
671 fromInteger n = pure (fromInteger n)
681instance (MonadAsync m, Fractional a) => Fractional (ZipAsync m a) where
682 fromRational n = pure (fromRational n)
688instance (MonadAsync m, Floating a) => Floating (ZipAsync m a) where
708 logBase = liftA2 logBase
710-------------------------------------------------------------------------------
711-- Type adapting combinators
712-------------------------------------------------------------------------------
714-- | Adapt one streaming type to another.
715adapt :: (Streaming t1, Streaming t2) => t1 m a -> t2 m a
716adapt = fromStream . toStream
718-- | Interpret an ambiguously typed stream as 'StreamT'.
719serially :: StreamT m a -> StreamT m a
722-- | Interpret an ambiguously typed stream as 'InterleavedT'.
723interleaving :: InterleavedT m a -> InterleavedT m a
726-- | Interpret an ambiguously typed stream as 'AsyncT'.
727asyncly :: AsyncT m a -> AsyncT m a
730-- | Interpret an ambiguously typed stream as 'ParallelT'.
731parallely :: ParallelT m a -> ParallelT m a
734-- | Interpret an ambiguously typed stream as 'ZipStream'.
735zipping :: ZipStream m a -> ZipStream m a
738-- | Interpret an ambiguously typed stream as 'ZipAsync'.
739zippingAsync :: ZipAsync m a -> ZipAsync m a
742------------------------------------------------------------------------------
743-- Constructing a stream
744------------------------------------------------------------------------------
746cons :: (Streaming t) => a -> t m a -> t m a
747cons a r = fromStream $ scons a (Just (toStream r))
749nil :: Streaming t => t m a
750nil = fromStream $ snil
752-- | Build a stream from its church encoding. The function passed maps
753-- directly to the underlying representation of the stream type. The second
754-- parameter to the function is the "yield" function yielding a value and the
755-- remaining stream if any otherwise 'Nothing'. The third parameter is to
756-- represent an "empty" stream.
757streamBuild :: Streaming t
758 => (forall r. Maybe (SVar m a)
759 -> (a -> Maybe (t m a) -> m r)
763streamBuild k = fromStream $ Stream $ \sv stp yld ->
764 let yield a Nothing = yld a Nothing
765 yield a (Just r) = yld a (Just (toStream r))
768-- | Build a singleton stream from a callback function.
769fromCallback :: (Streaming t) => (forall r. (a -> m r) -> m r) -> t m a
770fromCallback k = fromStream $ Stream $ \_ _ yld -> k (\a -> yld a Nothing)
772-- | Read an SVar to get a stream.
773fromSVar :: (MonadAsync m, Streaming t) => SVar m a -> t m a
774fromSVar sv = fromStream $ fromStreamVar sv
776------------------------------------------------------------------------------
777-- Destroying a stream
778------------------------------------------------------------------------------
780-- | Fold a stream using its church encoding. The second argument is the "step"
781-- function consuming an element and the remaining stream, if any. The third
782-- argument is for consuming an "empty" stream that yields nothing.
783streamFold :: Streaming t
784 => Maybe (SVar m a) -> (a -> Maybe (t m a) -> m r) -> m r -> t m a -> m r
785streamFold sv step blank m =
786 let yield a Nothing = step a Nothing
787 yield a (Just x) = step a (Just (fromStream x))
788 in (runStream (toStream m)) sv blank yield
790-- | Run a streaming composition, discard the results.
791runStreaming :: (Monad m, Streaming t) => t m a -> m ()
792runStreaming m = go (toStream m)
796 yield _ Nothing = stop
797 yield _ (Just x) = go x
798 in (runStream m1) Nothing stop yield
800-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
801-- be read back from the SVar using 'fromSVar'.
802toSVar :: (Streaming t, MonadAsync m) => SVar m a -> t m a -> m ()
803toSVar sv m = toStreamVar sv (toStream m)
805-------------------------------------------------------------------------------
806-- Running Streams, convenience functions specialized to types
807-------------------------------------------------------------------------------
809-- | Run a 'StreamT' computation. Same as @runStreaming . serially@.
810runStreamT :: Monad m => StreamT m a -> m ()
811runStreamT = runStreaming
813-- | Run an 'InterleavedT' computation. Same as @runStreaming . interleaving@.
814runInterleavedT :: Monad m => InterleavedT m a -> m ()
815runInterleavedT = runStreaming
817-- | Run an 'AsyncT' computation. Same as @runStreaming . asyncly@.
818runAsyncT :: Monad m => AsyncT m a -> m ()
819runAsyncT = runStreaming
821-- | Run a 'ParallelT' computation. Same as @runStreaming . parallely@.
822runParallelT :: Monad m => ParallelT m a -> m ()
823runParallelT = runStreaming
825-- | Run a 'ZipStream' computation. Same as @runStreaming . zipping@.
826runZipStream :: Monad m => ZipStream m a -> m ()
827runZipStream = runStreaming
829-- | Run a 'ZipAsync' computation. Same as @runStreaming . zippingAsync@.
830runZipAsync :: Monad m => ZipAsync m a -> m ()
831runZipAsync = runStreaming
833------------------------------------------------------------------------------
835------------------------------------------------------------------------------
837-- XXX Get rid of this?
838-- | Make a stream asynchronous, triggers the computation and returns a stream
839-- in the underlying monad representing the output generated by the original
840-- computation. The returned action is exhaustible and must be drained once. If
841-- not drained fully we may have a thread blocked forever and once exhausted it
842-- will always return 'empty'.
844async :: (Streaming t, MonadAsync m) => t m a -> m (t m a)
846 sv <- newStreamVar1 (SVarStyle Disjunction LIFO) (toStream m)
849------------------------------------------------------------------------------
850-- Sum Style Composition
851------------------------------------------------------------------------------
855-- | Sequential interleaved composition, in contrast to '<>' this operator
856-- fairly interleaves the two streams instead of appending them; yielding one
857-- element from each stream alternately. Unlike '<>' it cannot be used to fold
858-- an infinite container of streams.
860(<=>) :: Streaming t => t m a -> t m a -> t m a
861m1 <=> m2 = fromStream $ interleave (toStream m1) (toStream m2)
863-- | Parallel interleaved composition, in contrast to '<|>' this operator
864-- "merges" streams in a left biased manner rather than fairly interleaving
865-- them. It keeps yielding from the stream on the left as long as it can. If
866-- the left stream blocks or cannot keep up with the pace of the consumer it
867-- can yield from the stream on the right in parallel. Unlike '<|>' it can be
868-- used to fold infinite containers of streams.
870(<|) :: (Streaming t, MonadAsync m) => t m a -> t m a -> t m a
871m1 <| m2 = fromStream $ parLeft (toStream m1) (toStream m2)
873------------------------------------------------------------------------------
875------------------------------------------------------------------------------
878-- These utilities are designed to pass the first argument as one of the sum
879-- style composition operators (i.e. '<>', '<=>', '<|', '<|>') to conveniently
880-- fold a container using any style of stream composition.
882-- | Like the 'Prelude' 'fold' but allows you to specify a binary sum style
883-- stream composition operator to fold a container of streams.
885-- @foldWith (<>) $ map return [1..3]@
886{-# INLINABLE foldWith #-}
887foldWith :: (Streaming t, Foldable f)
888 => (t m a -> t m a -> t m a) -> f (t m a) -> t m a
889foldWith f = foldr f nil
891-- | Like 'foldMap' but allows you to specify a binary sum style composition
892-- operator to fold a container of streams. Maps a monadic streaming action on
893-- the container before folding it.
895-- @foldMapWith (<>) return [1..3]@
896{-# INLINABLE foldMapWith #-}
897foldMapWith :: (Streaming t, Foldable f)
898 => (t m b -> t m b -> t m b) -> (a -> t m b) -> f a -> t m b
899foldMapWith f g = foldr (f . g) nil
901-- | Like 'foldMapWith' but with the last two arguments reversed i.e. the
902-- monadic streaming function is the last argument.
903{-# INLINABLE forEachWith #-}
904forEachWith :: (Streaming t, Foldable f)
905 => (t m b -> t m b -> t m b) -> f a -> (a -> t m b) -> t m b
906forEachWith f xs g = foldr (f . g) nil xs
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