streamly 0.7.0 → 0.7.1
raw patch · 139 files changed
+25550/−17163 lines, 139 filesdep +fusion-plugindep +fusion-plugin-typesdep +ghcdep ~containersdep ~directorydep ~randomnew-uploaderbinary-added
Dependencies added: fusion-plugin, fusion-plugin-types, ghc, inspection-and-dev-flags-cannot-be-used-together, primitive
Dependency ranges changed: containers, directory, random
Files
- Changelog.md +32/−1
- README.md +17/−20
- bench.sh +139/−29
- benchmark/BaseStreams.hs +28/−1
- benchmark/Chart.hs +101/−13
- benchmark/Common.hs +95/−0
- benchmark/Concurrent.hs +32/−25
- benchmark/FileIO.hs +32/−9
- benchmark/Linear.hs +389/−257
- benchmark/LinearAsync.hs +102/−60
- benchmark/LinearRate.hs +19/−11
- benchmark/NanoBenchmarks.hs +2/−2
- benchmark/Nested.hs +33/−68
- benchmark/NestedConcurrent.hs +84/−0
- benchmark/NestedOps.hs +42/−32
- benchmark/NestedUnfold.hs +17/−11
- benchmark/NestedUnfoldOps.hs +34/−36
- benchmark/Parallel.hs +93/−0
- benchmark/StreamDKOps.hs +4/−5
- benchmark/StreamDOps.hs +5/−1
- benchmark/StreamKOps.hs +2/−2
- benchmark/Streamly/Benchmark/Data/Array.hs +96/−0
- benchmark/Streamly/Benchmark/Data/ArrayOps.hs +148/−0
- benchmark/Streamly/Benchmark/Data/Prim/Array.hs +100/−0
- benchmark/Streamly/Benchmark/Data/Prim/ArrayOps.hs +153/−0
- benchmark/Streamly/Benchmark/Data/SmallArray.hs +95/−0
- benchmark/Streamly/Benchmark/Data/SmallArrayOps.hs +136/−0
- benchmark/Streamly/Benchmark/FileIO/Array.hs +259/−0
- benchmark/Streamly/Benchmark/FileIO/Stream.hs +651/−0
- benchmark/Streamly/Benchmark/Prelude.hs +1202/−0
- credits/CONTRIBUTORS.md +20/−10
- credits/COPYRIGHTS.md +9/−0
- credits/pipes-concurrency.txt +0/−24
- credits/primitive-0.7.0.0.txt +30/−0
- design/dfa-bytes.png binary
- design/dfa-classes.png binary
- design/dfa-rearr.png binary
- design/inlining.md +123/−0
- design/linked-lists.md +32/−0
- design/module-organization.md +134/−0
- design/utf8-decoder.md +603/−0
- docs/Build.md +91/−0
- examples/ControlFlow.hs +1/−1
- examples/HandleIO.hs +52/−26
- examples/ListDir.hs +12/−18
- src/Streamly.hs +62/−43
- src/Streamly/Benchmark/FileIO/Array.hs +0/−242
- src/Streamly/Benchmark/FileIO/Stream.hs +0/−617
- src/Streamly/Benchmark/Prelude.hs +0/−912
- src/Streamly/Data/Array.hs +43/−0
- src/Streamly/Data/Fold.hs +10/−1
- src/Streamly/Data/Prim/Array.hs +44/−0
- src/Streamly/Data/SmallArray.hs +34/−0
- src/Streamly/Data/Unfold.hs +8/−11
- src/Streamly/Data/Unicode/Stream.hs +3/−2
- src/Streamly/FileSystem/FD.hs +7/−11
- src/Streamly/FileSystem/FDIO.hs +0/−3
- src/Streamly/FileSystem/Handle.hs +0/−4
- src/Streamly/Internal/BaseCompat.hs +29/−0
- src/Streamly/Internal/Control/Monad.hs +28/−0
- src/Streamly/Internal/Data/Array.hs +203/−0
- src/Streamly/Internal/Data/Atomics.hs +0/−1
- src/Streamly/Internal/Data/Fold.hs +139/−10
- src/Streamly/Internal/Data/Fold/Types.hs +0/−1
- src/Streamly/Internal/Data/List.hs +4/−5
- src/Streamly/Internal/Data/Pipe.hs +7/−8
- src/Streamly/Internal/Data/Pipe/Types.hs +0/−1
- src/Streamly/Internal/Data/Prim/Array.hs +205/−0
- src/Streamly/Internal/Data/Prim/Array/Types.hs +943/−0
- src/Streamly/Internal/Data/SVar.hs +92/−16
- src/Streamly/Internal/Data/Sink.hs +0/−2
- src/Streamly/Internal/Data/Sink/Types.hs +3/−5
- src/Streamly/Internal/Data/SmallArray.hs +184/−0
- src/Streamly/Internal/Data/SmallArray/Types.hs +834/−0
- src/Streamly/Internal/Data/Stream/Ahead.hs +733/−0
- src/Streamly/Internal/Data/Stream/Async.hs +998/−0
- src/Streamly/Internal/Data/Stream/Combinators.hs +217/−0
- src/Streamly/Internal/Data/Stream/Enumeration.hs +550/−0
- src/Streamly/Internal/Data/Stream/Instances.hs +168/−0
- src/Streamly/Internal/Data/Stream/Parallel.hs +543/−0
- src/Streamly/Internal/Data/Stream/Prelude.hs +339/−0
- src/Streamly/Internal/Data/Stream/SVar.hs +240/−0
- src/Streamly/Internal/Data/Stream/Serial.hs +472/−0
- src/Streamly/Internal/Data/Stream/StreamD.hs +4271/−0
- src/Streamly/Internal/Data/Stream/StreamD/Type.hs +72/−23
- src/Streamly/Internal/Data/Stream/StreamDK.hs +165/−0
- src/Streamly/Internal/Data/Stream/StreamDK/Type.hs +108/−0
- src/Streamly/Internal/Data/Stream/StreamK.hs +1095/−0
- src/Streamly/Internal/Data/Stream/StreamK/Type.hs +989/−0
- src/Streamly/Internal/Data/Stream/Zip.hs +266/−0
- src/Streamly/Internal/Data/Strict.hs +5/−7
- src/Streamly/Internal/Data/Time.hs +0/−2
- src/Streamly/Internal/Data/Time/Clock.hsc +0/−1
- src/Streamly/Internal/Data/Time/Units.hs +0/−1
- src/Streamly/Internal/Data/Unfold.hs +160/−4
- src/Streamly/Internal/Data/Unfold/Types.hs +0/−1
- src/Streamly/Internal/Data/Unicode/Char.hs +0/−1
- src/Streamly/Internal/Data/Unicode/Stream.hs +536/−22
- src/Streamly/Internal/FileSystem/Dir.hs +2/−3
- src/Streamly/Internal/FileSystem/File.hs +3/−4
- src/Streamly/Internal/FileSystem/Handle.hs +34/−8
- src/Streamly/Internal/Memory/Array.hs +49/−7
- src/Streamly/Internal/Memory/Array/Types.hs +3/−4
- src/Streamly/Internal/Memory/ArrayStream.hs +4/−5
- src/Streamly/Internal/Memory/Unicode/Array.hs +0/−1
- src/Streamly/Internal/Mutable/Prim/Var.hs +88/−0
- src/Streamly/Internal/Network/Inet/TCP.hs +94/−29
- src/Streamly/Internal/Network/Socket.hs +59/−42
- src/Streamly/Internal/Prelude.hs +4398/−3697
- src/Streamly/Memory/Array.hs +1/−5
- src/Streamly/Memory/Malloc.hs +2/−6
- src/Streamly/Memory/Ring.hs +26/−15
- src/Streamly/Network/Socket.hs +1/−1
- src/Streamly/Prelude.hs +2/−2
- src/Streamly/Streams/Ahead.hs +0/−700
- src/Streamly/Streams/Async.hs +0/−860
- src/Streamly/Streams/Combinators.hs +0/−217
- src/Streamly/Streams/Enumeration.hs +0/−550
- src/Streamly/Streams/Instances.hs +0/−134
- src/Streamly/Streams/Parallel.hs +0/−511
- src/Streamly/Streams/Prelude.hs +0/−327
- src/Streamly/Streams/SVar.hs +0/−119
- src/Streamly/Streams/Serial.hs +0/−399
- src/Streamly/Streams/StreamD.hs +0/−3950
- src/Streamly/Streams/StreamDK.hs +0/−165
- src/Streamly/Streams/StreamDK/Type.hs +0/−108
- src/Streamly/Streams/StreamK.hs +0/−1037
- src/Streamly/Streams/StreamK/Type.hs +0/−1043
- src/Streamly/Streams/Zip.hs +0/−236
- src/Streamly/Streams/inline.hs +0/−27
- src/inline.hs +27/−0
- stack.yaml +3/−1
- streamly.cabal +388/−216
- test/Arrays.hs +0/−115
- test/Prop.hs +21/−0
- test/Streamly/Test/Array.hs +178/−0
- test/Streamly/Test/Internal/Data/Fold.hs +27/−0
- test/Streamly/Test/Internal/Prelude.hs +78/−0
- test/version-bounds.hs +4/−0
Changelog.md view
@@ -1,3 +1,32 @@+## 0.7.1++### Bug Fixes++* Fix a bug that caused `findIndices` to return wrong indices in some+ cases.+* Fix a bug in `tap`, `chunksOf` that caused memory consumption to+ increase in some cases.+* Fix a space leak in concurrent streams (`async`, `wAsync`, and `ahead`) that+ caused memory consumption to increase with the number of elements in the+ stream, especially when built with `-threaded` and used with `-N` RTS option.+ The issue occurs only in cases when a worker thread happens to be used+ continuously for a long time.+* Fix scheduling of WAsyncT stream style to be in round-robin fashion.+* Now builds with `containers` package version < 0.5.8.+* Now builds with `network` package version >= 3.0.0.0 && < 3.1.0.0.++### Behavior change++* Combinators in `Streamly.Network.Inet.TCP` no longer use TCP `NoDelay` and+ `ReuseAddr` socket options by default. These options can now be specified+ using appropriate combinators.++### Performance++* Now uses `fusion-plugin` package for predictable stream fusion optimizations+* Significant improvement in performance of concurrent stream operations.+* Improved space and time performance of `Foldable` instance.+ ## 0.7.0 ### Breaking changes@@ -43,6 +72,8 @@ * Fix a bug that caused `uniq` function to yield the same element twice. * Fix a bug that caused "thread blocked indefinitely in an MVar operation" exception in a parallel stream.+* Fix unbounded memory usage (leak) in `parallel` combinator. The bug manifests+ when large streams are combined using `parallel`. ### Major Enhancements @@ -348,7 +379,7 @@ * Add `iterate`, `iterateM` stream operations ### Bug Fixes-* Fixed a bug that casued unexpected behavior when `pure` was used to inject+* Fixed a bug that caused unexpected behavior when `pure` was used to inject values in Applicative composition of `ZipStream` and `ZipAsync` types. ## 0.1.1
README.md view
@@ -1,5 +1,20 @@ # Streamly +[](https://hackage.haskell.org/package/streamly)+[](https://gitter.im/composewell/streamly)+[](https://travis-ci.com/composewell/streamly)+[](https://ci.appveyor.com/project/harendra-kumar/streamly)+[](https://circleci.com/gh/composewell/streamly/tree/master)+[](https://coveralls.io/github/composewell/streamly?branch=master)++## Learning Materials++* Documentation: [Quick](#streaming-concurrently) | [Tutorial](https://hackage.haskell.org/package/streamly/docs/Streamly-Tutorial.html) | [Reference (Hackage)](https://hackage.haskell.org/package/streamly) | [Reference (Latest)](https://composewell.github.io/streamly) | [Guides](docs)+* Installing: [Installing](./INSTALL.md) | [Building for optimal performance](docs/Build.md)+* Examples: [streamly](examples) | [streamly-examples](https://github.com/composewell/streamly-examples)+* Benchmarks: [Streaming](https://github.com/composewell/streaming-benchmarks) | [Concurrency](https://github.com/composewell/concurrency-benchmarks)+* Talks: [Functional Conf 2019 Video](https://www.youtube.com/watch?v=uzsqgdMMgtk) | [Functional Conf 2019 Slides](https://www.slideshare.net/HarendraKumar10/streamly-concurrent-data-flow-programming)+ ## Streaming Concurrently Haskell lists express pure computations using composable stream operations like@@ -87,7 +102,7 @@ ## Installing and using -Please see [INSTALL.md](INSTALL.md) for instructions on how to use streamly+Please see [INSTALL.md](./INSTALL.md) for instructions on how to use streamly with your Haskell build tool or package manager. You may want to go through it before jumping to run the examples below. @@ -500,7 +515,7 @@ supposed to work in non-concurrent code. When concurrent streams are combined together, exceptions from the constituent streams are propagated to the consumer stream. When an exception occurs in any of the constituent-streams other concurrent streams are promptly terminated. +streams other concurrent streams are promptly terminated. There is no notion of explicit threads in streamly, therefore, no asynchronous exceptions to deal with. You can just ignore the zillions of@@ -568,24 +583,6 @@ See the `Comparison with existing packages` section at the end of the [tutorial](https://hackage.haskell.org/package/streamly/docs/Streamly-Tutorial.html).--## Further Reading--For more information, see:-- * [Detailed tutorial](https://hackage.haskell.org/package/streamly/docs/Streamly-Tutorial.html)- * [Reference documentation](https://hackage.haskell.org/package/streamly)- * [Examples](examples)- * [Guides](docs)- * [Streaming benchmarks](https://github.com/composewell/streaming-benchmarks)- * [Concurrency benchmarks](https://github.com/composewell/concurrency-benchmarks)--For additional unreleased/experimental APIs, build the haddock docs using:--```-$ cabal haddock --haddock-option="--show-all"-$ stack haddock --haddock-arguments "--show-all" --no-haddock-deps-``` ## Support
bench.sh view
@@ -1,17 +1,41 @@ #!/bin/bash +SERIAL_BENCHMARKS="linear linear-rate nested nested-unfold base"+# parallel benchmark-suite is separated because we run it with a higher+# heap size limit.+CONCURRENT_BENCHMARKS="linear-async nested-concurrent parallel concurrent adaptive"+ARRAY_BENCHMARKS="array unpinned-array prim-array small-array"++INFINITE_BENCHMARKS="$SERIAL_BENCHMARKS linear-async nested-concurrent"+FINITE_BENCHMARKS="$ARRAY_BENCHMARKS fileio parallel concurrent adaptive"++QUICK_BENCHMARKS="linear-rate concurrent adaptive"+VIRTUAL_BENCHMARKS="array-cmp"++ALL_BENCHMARKS="$SERIAL_BENCHMARKS $CONCURRENT_BENCHMARKS $ARRAY_BENCHMARKS $VIRTUAL_BENCHMARKS"++list_benches () {+ for i in $ALL_BENCHMARKS+ do+ echo -n "|$i"+ done+}+ print_help () { echo "Usage: $0 "- echo " [--benchmarks <all|linear|linear-async|linear-rate|nested|concurrent|fileio|array|base>]"+ echo " [--benchmarks <ALL|SERIAL|CONCURRENT|ARRAY|INFINITE|FINITE|DEV$(list_benches)>]" echo " [--group-diff]" echo " [--graphs]" echo " [--no-measure]"- echo " [--append] "- echo " [--compare] [--base commit] [--candidate commit]"+ echo " [--append]"+ echo " [--long]" echo " [--slow]"- echo " -- <gauge options>"+ echo " [--quick]"+ echo " [--compare] [--base commit] [--candidate commit]"+ echo " [--cabal-build-flags]"+ echo " -- <gauge options or benchmarks>" echo- echo "Multiple benchmarks can be specified as a space separate list"+ echo "Multiple benchmarks can be specified as a space separated list" echo " e.g. --benchmarks \"linear nested\"" echo echo "--group-diff is used to compare groups within a single benchmark"@@ -21,7 +45,7 @@ echo "commit is generated, in the 'charts' directory." echo "Use --base and --candidate to select the commits to compare." echo- echo "Any arguments after a '--' are passed directly to guage"+ echo "Any arguments after a '--' are passed directly to gauge" exit } @@ -34,12 +58,23 @@ set_benchmarks() { if test -z "$BENCHMARKS" then- BENCHMARKS=$DEFAULT_BENCHMARKS- elif test "$BENCHMARKS" = "all"- then- BENCHMARKS=$ALL_BENCHMARKS+ echo $DEFAULT_BENCHMARKS+ else+ for i in $(echo $BENCHMARKS)+ do+ case $i in+ ALL) echo -n $ALL_BENCHMARKS ;;+ SERIAL) echo -n $SERIAL_BENCHMARKS ;;+ CONCURRENT) echo -n $CONCURRENT_BENCHMARKS ;;+ ARRAY) echo -n $ARRAY_BENCHMARKS ;;+ INFINITE) echo -n $INFINITE_BENCHMARKS ;;+ FINITE) echo -n $FINITE_BENCHMARKS ;;+ array-cmp) echo -n "$ARRAY_BENCHMARKS array-cmp" ;;+ *) echo -n $i ;;+ esac+ echo -n " "+ done fi- echo "Using benchmark suites [$BENCHMARKS]" } # $1: benchmark name (linear, nested, base)@@ -86,7 +121,7 @@ # We run the benchmarks in isolation in a separate process so that different # benchmarks do not interfere with other. To enable that we need to pass the-# benchmark exe path to guage as an argument. Unfortunately it cannot find its+# benchmark exe path to gauge as an argument. Unfortunately it cannot find its # own path currently. # The path is dependent on the architecture and cabal version.@@ -137,6 +172,7 @@ local bench_name=$1 local output_file=$(bench_output_file $bench_name) local bench_prog+ local quick_bench=0 bench_prog=$($GET_BENCH_PROG $bench_name) || \ die "Cannot find benchmark executable for benchmark $bench_name" @@ -144,6 +180,35 @@ echo "Running benchmark $bench_name ..." + for i in $QUICK_BENCHMARKS+ do+ if test "$(has_benchmark $i)" = "$bench_name"+ then+ quick_bench=1+ fi+ done++ local QUICK_OPTS="--quick --time-limit 1 --min-duration 0"+ local SPEED_OPTIONS+ if test "$LONG" -eq 0+ then+ if test "$SLOW" -eq 0+ then+ if test "$QUICK" -eq 0 -a "$quick_bench" -eq 0+ then+ # reasonably quick+ SPEED_OPTIONS="$QUICK_OPTS --min-samples 10"+ else+ # super quick but less accurate+ SPEED_OPTIONS="$QUICK_OPTS --include-first-iter"+ fi+ else+ SPEED_OPTIONS="--min-duration 0"+ fi+ else+ SPEED_OPTIONS="--stream-size 10000000 $QUICK_OPTS --include-first-iter"+ fi+ $bench_prog $SPEED_OPTIONS \ --csvraw=$output_file \ -v 2 \@@ -231,7 +296,6 @@ #----------------------------------------------------------------------------- DEFAULT_BENCHMARKS="linear"-ALL_BENCHMARKS="linear linear-async linear-rate nested concurrrent fileio array base" GROUP_DIFF=0 COMPARE=0@@ -239,14 +303,17 @@ CANDIDATE= APPEND=0+SLOW=0+QUICK=0+LONG=0 RAW=0 GRAPH=0 MEASURE=1-SPEED_OPTIONS="--quick --min-samples 10 --time-limit 1 --min-duration 0" GAUGE_ARGS= BUILD_ONCE=0 USE_STACK=0+CABAL_BUILD_FLAGS="" GHC_VERSION=$(ghc --numeric-version) @@ -263,14 +330,17 @@ case $1 in -h|--help|help) print_help ;; # options with arguments- --slow) SPEED_OPTIONS="--min-duration 0"; shift ;; --benchmarks) shift; BENCHMARKS=$1; shift ;; --base) shift; BASE=$1; shift ;; --candidate) shift; CANDIDATE=$1; shift ;;+ --cabal-build-flags) shift; CABAL_BUILD_FLAGS=$1; shift ;; # flags+ --slow) SLOW=1; shift ;;+ --quick) QUICK=1; shift ;; --compare) COMPARE=1; shift ;; --raw) RAW=1; shift ;; --append) APPEND=1; shift ;;+ --long) LONG=1; shift ;; --group-diff) GROUP_DIFF=1; shift ;; --graphs) GRAPH=1; shift ;; --no-measure) MEASURE=0; shift ;;@@ -281,34 +351,58 @@ done GAUGE_ARGS=$* -echo "Using stack command [$STACK]"-set_benchmarks--if echo "$BENCHMARKS" | grep -q base+BENCHMARKS=$(set_benchmarks)+if test "$LONG" -ne 0 then- STACK_BUILD_FLAGS="--flag streamly:dev"- CABAL_BUILD_FLAGS="--flags dev"+ BENCHMARKS=$INFINITE_BENCHMARKS fi -if echo "$BENCHMARKS" | grep -q concurrent-then- STACK_BUILD_FLAGS="--flag streamly:dev"- CABAL_BUILD_FLAGS="--flags dev"-fi+only_real_benchmarks () {+ for i in $BENCHMARKS+ do+ local SKIP=0+ for j in $VIRTUAL_BENCHMARKS+ do+ if test $i == $j+ then+ SKIP=1+ fi+ done+ if test "$SKIP" -eq 0+ then+ echo -n "$i "+ fi+ done+} +BENCHMARKS_ORIG=$BENCHMARKS+BENCHMARKS=$(only_real_benchmarks)+echo "Using benchmark suites [$BENCHMARKS]"++has_benchmark () {+ for i in $BENCHMARKS_ORIG+ do+ if test "$i" = "$1"+ then+ echo "$i"+ break+ fi+ done+}+ if test "$USE_STACK" = "1" then WHICH_COMMAND="stack exec which" BUILD_CHART_EXE="stack build --flag streamly:dev" GET_BENCH_PROG=stack_bench_prog- BUILD_BENCH="stack build $STACK_BUILD_FLAGS --flags "streamly:benchmark" --bench --no-run-benchmarks"+ BUILD_BENCH="stack build $STACK_BUILD_FLAGS --bench --no-run-benchmarks" else # XXX cabal issue "cabal v2-exec which" cannot find benchmark/test executables #WHICH_COMMAND="cabal v2-exec which" WHICH_COMMAND=cabal_which BUILD_CHART_EXE="cabal v2-build --flags dev chart" GET_BENCH_PROG=cabal_bench_prog- BUILD_BENCH="cabal v2-build $CABAL_BUILD_FLAGS --flag benchmark --enable-benchmarks"+ BUILD_BENCH="cabal v2-build $CABAL_BUILD_FLAGS --enable-benchmarks" fi #-----------------------------------------------------------------------------@@ -326,7 +420,12 @@ if test "$MEASURE" = "1" then echo $BUILD_BENCH- $BUILD_BENCH || die "build failed"+ if test "$USE_STACK" = "1"+ then+ $BUILD_BENCH || die "build failed"+ else+ $BUILD_BENCH $BENCHMARKS || die "build failed"+ fi run_measurements "$BENCHMARKS" fi @@ -334,7 +433,18 @@ # Run reports #----------------------------------------------------------------------------- +VIRTUAL_REPORTS=""+if test "$(has_benchmark 'array-cmp')" = "array-cmp"+then+ VIRTUAL_REPORTS="$VIRTUAL_REPORTS array-cmp"+ mkdir -p "charts/array-cmp"+ cat "charts/array/results.csv" \+ "charts/prim-array/results.csv" \+ "charts/unpinned-array/results.csv" > "charts/array-cmp/results.csv"+fi+ if test "$RAW" = "0" then run_reports "$BENCHMARKS"+ run_reports "$VIRTUAL_REPORTS" fi
benchmark/BaseStreams.hs view
@@ -7,7 +7,7 @@ {-# LANGUAGE CPP #-} -import Control.DeepSeq (NFData)+import Control.DeepSeq (NFData(..)) -- import Data.Functor.Identity (Identity, runIdentity) import System.Random (randomRIO) @@ -17,6 +17,10 @@ import qualified StreamDKOps as DK import qualified Data.List as List +#if !MIN_VERSION_deepseq(1,4,3)+instance NFData Ordering where rnf = (`seq` ())+#endif+ -- We need a monadic bind here to make sure that the function f does not get -- completely optimized out by the compiler in some cases. {-# INLINE benchIO #-}@@ -41,6 +45,7 @@ => String -> (t IO Int -> IO b) -> (Int -> t IO Int) -> Benchmark benchFold name f src = bench name $ nfIO $ randomRIO (1,1) >>= f . src +#ifdef DEVBUILD -- | Takes a source, and uses it with a default drain/fold method. {-# INLINE benchD #-} benchD :: String -> (Int -> D.Stream IO Int) -> Benchmark@@ -49,6 +54,7 @@ {-# INLINE benchK #-} benchK :: String -> (Int -> K.Stream IO Int) -> Benchmark benchK name f = bench name $ nfIO $ randomRIO (1,1) >>= K.toNull . f+#endif {- _benchId :: NFData b => String -> (Ops.Stream m Int -> Identity b) -> Benchmark@@ -75,10 +81,14 @@ [ benchIO "toNull" D.toNull D.sourceUnfoldrM , benchIO "mapM_" D.mapM_ D.sourceUnfoldrM , benchIO "uncons" D.uncons D.sourceUnfoldrM+#ifdef DEVBUILD+ -- XXX these consume too much stack space, need to fix or segregate in+ -- another benchmark. , benchFold "tail" D.tail D.sourceUnfoldrM , benchIO "nullTail" D.nullTail D.sourceUnfoldrM , benchIO "headTail" D.headTail D.sourceUnfoldrM , benchFold "toList" D.toList D.sourceUnfoldrM+#endif , benchFold "foldl'" D.foldl D.sourceUnfoldrM , benchFold "last" D.last D.sourceUnfoldrM ]@@ -186,6 +196,9 @@ , benchIO "filter-scan" (D.filterScan 4) D.sourceUnfoldrM , benchIO "filter-map" (D.filterMap 4) D.sourceUnfoldrM ]+#ifdef DEVBUILD+ -- XXX these consume too much stack space, need to fix or segregate in+ -- another benchmark. , bgroup "iterated" [ benchD "mapM" D.iterateMapM , benchD "scan(1/10)" D.iterateScan@@ -194,7 +207,10 @@ , benchD "dropOne" D.iterateDropOne , benchD "dropWhileFalse(1/10)" D.iterateDropWhileFalse , benchD "dropWhileTrue" D.iterateDropWhileTrue+ , benchD "iterateM" D.iterateM+ ]+#endif ] , bgroup "list" [ bgroup "elimination"@@ -226,10 +242,14 @@ , benchIO "mapM_" K.mapM_ K.sourceUnfoldrM , benchIO "uncons" K.uncons K.sourceUnfoldrM , benchFold "init" K.init K.sourceUnfoldrM+#ifdef DEVBUILD+ -- XXX these consume too much stack space, need to fix or segregate in+ -- another benchmark. , benchFold "tail" K.tail K.sourceUnfoldrM , benchIO "nullTail" K.nullTail K.sourceUnfoldrM , benchIO "headTail" K.headTail K.sourceUnfoldrM , benchFold "toList" K.toList K.sourceUnfoldrM+#endif , benchFold "foldl'" K.foldl K.sourceUnfoldrM , benchFold "last" K.last K.sourceUnfoldrM ]@@ -258,7 +278,10 @@ (K.sourceUnfoldrMN (K.value `div` 4)) , benchIO "intersperse" (K.intersperse 1) (K.sourceUnfoldrMN K.value2) , benchIO "interspersePure" (K.intersperse 1) (K.sourceUnfoldrN K.value2)+#ifdef DEVBUILD+ -- XXX this consumes too much heap , benchIO "foldlS" (K.foldlS 1) K.sourceUnfoldrM+#endif ] , bgroup "transformationX4" [ benchIO "scan" (K.scan 4) K.sourceUnfoldrM@@ -330,6 +353,9 @@ , benchIO "filter-scan" (K.filterScan 4) K.sourceUnfoldrM , benchIO "filter-map" (K.filterMap 4) K.sourceUnfoldrM ]+#ifdef DEVBUILD+ -- XXX these consume too much stack space, need to fix or segregate in+ -- another benchmark. , bgroup "iterated" [ benchK "mapM" K.iterateMapM , benchK "scan(1/10)" K.iterateScan@@ -339,6 +365,7 @@ , benchK "dropWhileFalse(1/10)" K.iterateDropWhileFalse , benchK "dropWhileTrue" K.iterateDropWhileTrue ]+#endif ] , bgroup "streamDK" [ bgroup "generation"
benchmark/Chart.hs view
@@ -7,6 +7,7 @@ import Control.Exception (handle, catch, SomeException, ErrorCall(..)) import Control.Monad.Trans.State import Control.Monad.Trans.Maybe+import Data.Char (toLower) import Data.Function (on, (&)) import Data.List import Data.List.Split@@ -27,10 +28,18 @@ | LinearAsync | LinearRate | Nested+ | NestedConcurrent+ | NestedUnfold | Base | FileIO | Array+ | ArrayCmp+ | UnpinnedArray+ | SmallArray+ | PrimArray | Concurrent+ | Parallel+ | Adaptive deriving Show data Options = Options@@ -69,10 +78,18 @@ Just "linear-async" -> setBenchType LinearAsync Just "linear-rate" -> setBenchType LinearRate Just "nested" -> setBenchType Nested+ Just "nested-concurrent" -> setBenchType NestedConcurrent+ Just "nested-unfold" -> setBenchType NestedUnfold Just "base" -> setBenchType Base Just "fileio" -> setBenchType FileIO+ Just "array-cmp" -> setBenchType ArrayCmp Just "array" -> setBenchType Array+ Just "unpinned-array" -> setBenchType UnpinnedArray+ Just "small-array" -> setBenchType SmallArray+ Just "prim-array" -> setBenchType PrimArray Just "concurrent" -> setBenchType Concurrent+ Just "parallel" -> setBenchType Parallel+ Just "adaptive" -> setBenchType Adaptive Just str -> do liftIO $ putStrLn $ "unrecognized benchmark type " <> str mzero@@ -243,15 +260,39 @@ makeFileIOGraphs cfg@Config{..} inputFile = ignoringErr $ graph inputFile "fileIO" cfg -makeArrayGraphs :: Config -> String -> IO ()-makeArrayGraphs cfg@Config{..} inputFile =- ignoringErr $ graph inputFile "array" cfg+------------------------------------------------------------------------------+-- Generic+------------------------------------------------------------------------------ -makeConcurrentGraphs :: Config -> String -> IO ()-makeConcurrentGraphs cfg@Config{..} inputFile =- ignoringErr $ graph inputFile "concurrent" cfg+makeGraphs :: String -> Config -> String -> IO ()+makeGraphs name cfg@Config{..} inputFile =+ ignoringErr $ graph inputFile name cfg ------------------------------------------------------------------------------+-- Arrays+------------------------------------------------------------------------------++showArrayComparisons Options{..} cfg inp out =+ let cfg' = cfg { classifyBenchmark = classifyArray }+ in if genGraphs+ then ignoringErr $ graph inp "Arrays Comparison"+ cfg' { outputDir = Just out+ , presentation = Groups Absolute+ }+ else ignoringErr $ report inp Nothing cfg'++ where++ classifyArray b+ -- SmallArray uses a small number of elements therefore cannot be+ -- compared+ -- | "SmallArray/" `isPrefixOf` b = ("SmallArray",) <$> stripPrefix "SmallArray/" b+ | "Data.Prim.Array/" `isPrefixOf` b = ("Data.Prim.Array",) <$> stripPrefix "Data.Prim.Array/" b+ | "Data.Array/" `isPrefixOf` b = ("Data.Array",) <$> stripPrefix "Data.Array/" b+ | "array/" `isPrefixOf` b = ("array",) <$> stripPrefix "array/" b+ | otherwise = Nothing++------------------------------------------------------------------------------ -- Reports/Charts for base streams ------------------------------------------------------------------------------ @@ -322,6 +363,12 @@ let cfg = defaultConfig { presentation = Groups PercentDiff , selectBenchmarks = selectBench+ , selectFields = filter+ ( flip elem ["time" , "mean"+ , "maxrss", "cputime"+ ]+ . map toLower+ ) } res <- parseOptions @@ -332,23 +379,35 @@ Just opts@Options{..} -> case benchType of Linear -> benchShow opts cfg- { title = Just "100,000 elems" }+ { title = Just "Linear" } makeLinearGraphs "charts/linear/results.csv" "charts/linear" LinearAsync -> benchShow opts cfg- { title = Just "Async 10,000 elems" }+ { title = Just "Linear Async" } makeLinearAsyncGraphs "charts/linear-async/results.csv" "charts/linear-async"- LinearRate -> benchShow opts cfg makeLinearRateGraphs+ LinearRate -> benchShow opts cfg+ { title = Just "Linear Rate" }+ makeLinearRateGraphs "charts/linear-rate/results.csv" "charts/linear-rate" Nested -> benchShow opts cfg- { title = Just "Nested loops 100 x 100 elems" }+ { title = Just "Nested loops" } makeNestedGraphs "charts/nested/results.csv" "charts/nested"+ NestedConcurrent -> benchShow opts cfg+ { title = Just "Nested concurrent loops" }+ makeNestedGraphs+ "charts/nested-concurrent/results.csv"+ "charts/nested-concurrent"+ NestedUnfold -> benchShow opts cfg+ { title = Just "Nested unfold loops" }+ makeNestedGraphs+ "charts/nested-unfold/results.csv"+ "charts/nested-unfold" FileIO -> benchShow opts cfg { title = Just "File IO" } makeFileIOGraphs@@ -356,16 +415,45 @@ "charts/fileio" Array -> benchShow opts cfg { title = Just "Array" }- makeArrayGraphs+ (makeGraphs "array") "charts/array/results.csv" "charts/array"+ UnpinnedArray -> benchShow opts cfg+ { title = Just "Unpinned Array" }+ (makeGraphs "unpinned-array")+ "charts/unpinned-array/results.csv"+ "charts/unpinned-array"+ SmallArray -> benchShow opts cfg+ { title = Just "Small Array" }+ (makeGraphs "small-array")+ "charts/small-array/results.csv"+ "charts/small-array"+ PrimArray -> benchShow opts cfg+ { title = Just "Prim Array" }+ (makeGraphs "prim-array")+ "charts/prim-array/results.csv"+ "charts/prim-array"+ ArrayCmp -> showArrayComparisons opts cfg+ { title = Just "Arrays Comparison" }+ "charts/array-cmp/results.csv"+ "charts/array-cmp" Concurrent -> benchShow opts cfg { title = Just "Concurrent Ops" }- makeConcurrentGraphs+ (makeGraphs "Concurrent") "charts/concurrent/results.csv" "charts/concurrent"+ Parallel -> benchShow opts cfg+ { title = Just "Parallel" }+ (makeGraphs "parallel")+ "charts/parallel/results.csv"+ "charts/parallel"+ Adaptive -> benchShow opts cfg+ { title = Just "Adaptive" }+ (makeGraphs "adaptive")+ "charts/adaptive/results.csv"+ "charts/adaptive" Base -> do- let cfg' = cfg { title = Just "100,000 elems" }+ let cfg' = cfg { title = Just "Base stream" } if groupDiff then showStreamDVsK opts cfg' "charts/base/results.csv"
+ benchmark/Common.hs view
@@ -0,0 +1,95 @@+-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com++module Common (parseCLIOpts) where++import Control.Exception (evaluate)+import Control.Monad (when)+import Data.List (scanl')+import Data.Maybe (catMaybes)+import System.Console.GetOpt+ (OptDescr(..), ArgDescr(..), ArgOrder(..), getOpt')+import System.Environment (getArgs, lookupEnv, setEnv)+import Text.Read (readMaybe)++import Gauge++-------------------------------------------------------------------------------+-- Parse custom CLI options+-------------------------------------------------------------------------------++data BenchOpts = StreamSize Int deriving Show++getStreamSize :: String -> Int+getStreamSize size =+ case (readMaybe size :: Maybe Int) of+ Just x -> x+ Nothing -> error "Stream size must be numeric"++options :: [OptDescr BenchOpts]+options =+ [+ Option [] ["stream-size"] (ReqArg getSize "COUNT") "Stream element count"+ ]++ where++ getSize = StreamSize . getStreamSize++deleteOptArgs+ :: (Maybe String, Maybe String) -- (prev, yielded)+ -> String+ -> (Maybe String, Maybe String)+deleteOptArgs (Nothing, _) opt =+ if opt == "--stream-size"+ then (Just opt, Nothing)+ else (Just opt, Just opt)++deleteOptArgs (Just prev, _) opt =+ if opt == "--stream-size" || prev == "--stream-size"+ then (Just opt, Nothing)+ else (Just opt, Just opt)++parseCLIOpts :: Int -> IO (Int, Config, [String])+parseCLIOpts defaultStreamSize = do+ args <- getArgs++ -- Parse custom options+ let (opts, _, _, errs) = getOpt' Permute options args+ when (not $ null errs) $ error $ concat errs+ (streamSize, args') <-+ case opts of+ StreamSize x : _ -> do+ -- When using the gauge "--measure-with" option we need to make+ -- sure that we pass the stream size to child process forked by+ -- gauge. So we use this env var for that purpose.+ setEnv "STREAM_SIZE" (show x)+ -- Hack! remove the option and its argument from args+ -- getOpt should have a way to return the unconsumed args in+ -- correct order.+ newArgs <-+ evaluate+ $ catMaybes+ $ map snd+ $ scanl' deleteOptArgs (Nothing, Nothing) args+ return (x, newArgs)+ _ -> do+ r <- lookupEnv "STREAM_SIZE"+ case r of+ Just x -> do+ s <- evaluate $ getStreamSize x+ return (s, args)+ Nothing -> return (defaultStreamSize, args)++ -- Parse gauge options+ let config = defaultConfig+ { timeLimit = Just 1+ , minDuration = 0+ , includeFirstIter = streamSize > defaultStreamSize+ }+ let (cfg, benches) = parseWith config args'+ streamSize `seq` return (streamSize, cfg, benches)
benchmark/Concurrent.hs view
@@ -7,8 +7,7 @@ -- Maintainer : streamly@composewell.com import Control.Concurrent-import Control.Monad (when)--- import Data.IORef+import Control.Monad (when, replicateM) import Gauge import Streamly@@ -18,7 +17,10 @@ -- Append ------------------------------------------------------------------------------- --- Single work item yielded per thread+-- | Run @tcount@ number of actions concurrently using the given concurrency+-- style. Each thread produces a single output after a delay of @d@+-- microseconds.+-- {-# INLINE append #-} append :: IsStream t => Int -> Int -> Int -> (t IO Int -> SerialT IO Int) -> IO ()@@ -30,11 +32,22 @@ $ maxThreads (-1) $ S.fromFoldableM $ map work [1..tcount] --- Big stream of items yielded per thread+-- | Run @threads@ concurrently, each producing streams of @elems@ elements+-- with a delay of @d@ microseconds between successive elements, and merge+-- their outputs in a single output stream. The individual streams are produced+-- serially but merged using the provided concurrency style.+-- {-# INLINE concated #-}-concated :: Int -> Int -> Int -> Int -> (forall a. SerialT IO a -> SerialT IO a -> SerialT IO a) -> IO ()+concated+ :: Int+ -> Int+ -> Int+ -> Int+ -> (forall a. SerialT IO a -> SerialT IO a -> SerialT IO a)+ -> IO () concated buflen threads d elems t =- let work = (\i -> (S.replicateM i ((when (d /= 0) (threadDelay d)) >> return i)))+ let work = \i -> S.replicateM i+ ((when (d /= 0) (threadDelay d)) >> return i) in S.drain $ adapt $ maxThreads (-1)@@ -72,25 +85,19 @@ [ -- bgroup "append/buf-1-threads-10k-0sec" (appendGroup 1 10000 0) -- , bgroup "append/buf-100-threads-100k-0sec" (appendGroup 100 100000 0)- bgroup "append/buf-10k-threads-10k-5sec" (appendGroup 10000 10000 5000000)+ bgroup "stream1x10k/buf10k-threads10k-5sec" (appendGroup 10000 10000 5000000) -- bgroup "concat/buf-1-threads-100k-count-1" (concatGroup 1 100000 0 1) -- bgroup "concat/buf-1-threads-1-count-10m" (concatGroup 1 1 0 10000000)- , bgroup "concat/buf-100-threads-100-count-500k" (concatGroup 100 100 0 500000)- {-- , bgroup "forkIO-5000ms-10k" $- let delay = threadDelay 5000000- count = 10000 :: Int- list = [1..count]- work i = delay >> return i- in- [ bench "discard" $ nfIO $ do- mapM_ (\i -> forkIO $ work i >> return ()) list- threadDelay 6000000- , bench "collect" $ nfIO $ do- ref <- newIORef []- mapM_ (\i -> forkIO $ work i >>=- \j -> atomicModifyIORef ref $ \xs -> (j : xs, ())) list- threadDelay 6000000- ]- -}+ , bgroup "streams100x500k/buf100-threads100" (concatGroup 100 100 0 500000)++ , bench "forkIO/threads10k-5sec" $+ let delay = threadDelay 5000000+ count = 10000 :: Int+ list = [1..count]+ work i = delay >> return i+ in nfIO $ do+ ref <- newEmptyMVar+ mapM_ (\i -> forkIO $ work i >>=+ \j -> putMVar ref j) list+ replicateM 10000 (takeMVar ref) ]
benchmark/FileIO.hs view
@@ -30,6 +30,9 @@ blockSize = 32768 blockCount = 3200 +fileSize :: Int+fileSize = blockSize * blockCount+ #ifdef DEVBUILD -- This is a 500MB text file for text processing benchmarks. We cannot -- have it in the repo, therefore we use it locally with DEVBUILD@@ -38,9 +41,6 @@ infile :: String infile = "benchmark/text-processing/gutenberg-500.txt" -fileSize :: Int-fileSize = blockSize * blockCount- #else infile :: String infile = scratchDir ++ "in-100MB.txt"@@ -91,9 +91,15 @@ , mkBench "catBracket" href $ do Handles inh _ <- readIORef href BFA.catBracket devNull inh+ , mkBench "catBracketIO" href $ do+ Handles inh _ <- readIORef href+ BFA.catBracketIO devNull inh , mkBench "catBracketStream" href $ do Handles inh _ <- readIORef href BFA.catBracketStream devNull inh+ , mkBench "catBracketStreamIO" href $ do+ Handles inh _ <- readIORef href+ BFA.catBracketStreamIO devNull inh , mkBench "catOnException" href $ do Handles inh _ <- readIORef href BFA.catOnException devNull inh@@ -142,15 +148,27 @@ , mkBench "catFinally" href $ do Handles inh _ <- readIORef href BFS.catFinally devNull inh+ , mkBench "catFinallyIO" href $ do+ Handles inh _ <- readIORef href+ BFS.catFinallyIO devNull inh , mkBench "catFinallyStream" href $ do Handles inh _ <- readIORef href BFS.catFinallyStream devNull inh+ , mkBench "catFinallyStreamIO" href $ do+ Handles inh _ <- readIORef href+ BFS.catFinallyStreamIO devNull inh , mkBench "catBracketStream" href $ do Handles inh _ <- readIORef href BFS.catBracketStream devNull inh+ , mkBench "catBracketStreamIO" href $ do+ Handles inh _ <- readIORef href+ BFS.catBracketStreamIO devNull inh , mkBench "catBracket" href $ do Handles inh _ <- readIORef href BFS.catBracket devNull inh+ , mkBench "catBracketIO" href $ do+ Handles inh _ <- readIORef href+ BFS.catBracketIO devNull inh #endif , mkBench "read-word8" href $ do Handles inh _ <- readIORef href@@ -186,21 +204,26 @@ Handles inh outh <- readIORef href BFS.copyCodecUtf8Lenient inh outh ]- , bgroup "grouping"- [ mkBench "chunksOf (single chunk)" href $ do+#endif+ , bgroup "grouping-chunks"+ [ mkBench "sumChunksOf (single chunk)" href $ do Handles inh _ <- readIORef href- BFS.chunksOf fileSize inh+ BFS.chunksOfSum fileSize inh+ , mkBench "sumChunksOf 1" href $ do+ Handles inh _ <- readIORef href+ BFS.chunksOfSum 1 inh - , mkBench "chunksOf 1" href $ do+ , mkBench "arraysOf 1" href $ do Handles inh _ <- readIORef href BFS.chunksOf 1 inh- , mkBench "chunksOf 10" href $ do+ , mkBench "arraysOf 10" href $ do Handles inh _ <- readIORef href BFS.chunksOf 10 inh- , mkBench "chunksOf 1000" href $ do+ , mkBench "arraysOf 1000" href $ do Handles inh _ <- readIORef href BFS.chunksOf 1000 inh ]+#ifdef DEVBUILD , bgroup "group-ungroup-stream" [ mkBench "lines-unlines-[Char]" href $ do Handles inh outh <- readIORef href
benchmark/Linear.hs view
@@ -11,10 +11,13 @@ -- Maintainer : streamly@composewell.com import Control.DeepSeq (NFData(..), deepseq)+import Control.Monad (when) import Data.Functor.Identity (Identity, runIdentity)-import System.Random (randomRIO) import Data.Monoid (Last(..))+import System.Random (randomRIO) +import Common (parseCLIOpts)+ import qualified GHC.Exts as GHC import qualified Streamly.Benchmark.Prelude as Ops @@ -24,6 +27,8 @@ import qualified Streamly.Prelude as S import qualified Streamly.Internal.Data.Sink as Sink +import Streamly.Internal.Data.Time.Units+import qualified Streamly.Internal.Memory.Array as IA import qualified Streamly.Internal.Data.Fold as IFL import qualified Streamly.Internal.Prelude as IP import qualified Streamly.Internal.Data.Pipe as Pipe@@ -45,15 +50,15 @@ {-# INLINE benchIOSink #-} benchIOSink :: (IsStream t, NFData b)- => String -> (t IO Int -> IO b) -> Benchmark-benchIOSink name f = bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.source+ => Int -> String -> (t IO Int -> IO b) -> Benchmark+benchIOSink value name f = bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.source value {-# INLINE benchHoistSink #-} benchHoistSink :: (IsStream t, NFData b)- => String -> (t Identity Int -> IO b) -> Benchmark-benchHoistSink name f =- bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.sourceUnfoldr+ => Int -> String -> (t Identity Int -> IO b) -> Benchmark+benchHoistSink value name f =+ bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.sourceUnfoldr value -- XXX once we convert all the functions to use this we can rename this to -- benchIOSink@@ -66,8 +71,8 @@ {-# INLINE benchIdentitySink #-} benchIdentitySink :: (IsStream t, NFData b)- => String -> (t Identity Int -> Identity b) -> Benchmark-benchIdentitySink name f = bench name $ nf (f . Ops.sourceUnfoldr) 1+ => Int -> String -> (t Identity Int -> Identity b) -> Benchmark+benchIdentitySink value name f = bench name $ nf (f . Ops.sourceUnfoldr value) 1 -- | Takes a source, and uses it with a default drain/fold method. {-# INLINE benchIOSrc #-}@@ -88,8 +93,8 @@ benchPure name src f = bench name $ nfIO $ randomRIO (1,1) >>= return . f . src {-# INLINE benchPureSink #-}-benchPureSink :: NFData b => String -> (SerialT Identity Int -> b) -> Benchmark-benchPureSink name f = benchPure name Ops.sourceUnfoldr f+benchPureSink :: NFData b => Int -> String -> (SerialT Identity Int -> b) -> Benchmark+benchPureSink value name f = benchPure name (Ops.sourceUnfoldr value) f -- XXX once we convert all the functions to use this we can rename this to -- benchPureSink@@ -101,343 +106,470 @@ {-# INLINE benchPureSinkIO #-} benchPureSinkIO :: NFData b- => String -> (SerialT Identity Int -> IO b) -> Benchmark-benchPureSinkIO name f =- bench name $ nfIO $ randomRIO (1, 1) >>= f . Ops.sourceUnfoldr+ => Int -> String -> (SerialT Identity Int -> IO b) -> Benchmark+benchPureSinkIO value name f =+ bench name $ nfIO $ randomRIO (1, 1) >>= f . Ops.sourceUnfoldr value {-# INLINE benchPureSrc #-} benchPureSrc :: String -> (Int -> SerialT Identity a) -> Benchmark benchPureSrc name src = benchPure name src (runIdentity . S.drain) -mkString :: String-mkString = "fromList [1" ++ concat (replicate Ops.value ",1") ++ "]"+mkString :: Int -> String+mkString value = "fromList [1" ++ concat (replicate value ",1") ++ "]" -mkListString :: String-mkListString = "[1" ++ concat (replicate Ops.value ",1") ++ "]"+mkListString :: Int -> String+mkListString value = "[1" ++ concat (replicate value ",1") ++ "]" -mkList :: [Int]-mkList = [1..Ops.value]+mkList :: Int -> [Int]+mkList value = [1..value] +defaultStreamSize :: Int+defaultStreamSize = 100000+ main :: IO ()-main =- defaultMain+main = do+ -- XXX Fix indentation+ (value, cfg, benches) <- parseCLIOpts defaultStreamSize+ let bufValue = min value defaultStreamSize+ when (bufValue /= value) $+ putStrLn $ "Limiting stream size to "+ ++ show defaultStreamSize+ ++ " for buffered operations"++ bufValue `seq` value `seq` runMode (mode cfg) cfg benches [ bgroup "serially" [ bgroup "pure"- [ benchPureSink "id" id- , benchPureSink1 "eqBy" Ops.eqByPure- , benchPureSink "==" Ops.eqInstance- , benchPureSink "/=" Ops.eqInstanceNotEq- , benchPureSink1 "cmpBy" Ops.cmpByPure- , benchPureSink "<" Ops.ordInstance- , benchPureSink "min" Ops.ordInstanceMin- , benchPureSrc "IsList.fromList" Ops.sourceIsList+ [ benchPureSink value "id" id+ , benchPureSink1 "eqBy" (Ops.eqByPure value)+ , benchPureSink value "==" Ops.eqInstance+ , benchPureSink value "/=" Ops.eqInstanceNotEq+ , benchPureSink1 "cmpBy" (Ops.cmpByPure value)+ , benchPureSink value "<" Ops.ordInstance+ , benchPureSink value "min" Ops.ordInstanceMin+ , benchPureSrc "IsList.fromList" (Ops.sourceIsList value) -- length is used to check for foldr/build fusion- , benchPureSink "length . IsList.toList" (length . GHC.toList)- , benchPureSrc "IsString.fromString" Ops.sourceIsString- , mkString `deepseq` (bench "readsPrec pure streams" $- nf Ops.readInstance mkString)- , mkString `deepseq` (bench "readsPrec Haskell lists" $- nf Ops.readInstanceList mkListString)- , benchPureSink "showsPrec pure streams" Ops.showInstance- , mkList `deepseq` (bench "showPrec Haskell lists" $- nf Ops.showInstanceList mkList)- , benchPureSink "foldl'" Ops.pureFoldl'- , benchPureSink "foldable/foldl'" Ops.foldableFoldl'- , benchPureSink "foldable/sum" Ops.foldableSum- , benchPureSinkIO "traversable/mapM" Ops.traversableMapM+ , benchPureSink value "length . IsList.toList" (length . GHC.toList)+ , benchPureSrc "IsString.fromString" (Ops.sourceIsString value)+ , benchPureSink value "showsPrec pure streams" Ops.showInstance+ , benchPureSink value "foldl'" Ops.pureFoldl' ]+ , bgroup "foldable"+ [ -- Foldable instance+ -- type class operations+ bench "foldl'" $ nf (Ops.foldableFoldl' value) 1+ , bench "foldrElem" $ nf (Ops.foldableFoldrElem value) 1+ -- , bench "null" $ nf (Ops.foldableNull value) 1+ , bench "elem" $ nf (Ops.foldableElem value) 1+ , bench "length" $ nf (Ops.foldableLength value) 1+ , bench "sum" $ nf (Ops.foldableSum value) 1+ , bench "product" $ nf (Ops.foldableProduct value) 1+ , bench "minimum" $ nf (Ops.foldableMin value) 1+ , bench "maximum" $ nf (Ops.foldableMax value) 1+ , bench "length . toList" $+ nf (length . Ops.foldableToList value) 1++ -- folds+ , bench "notElem" $ nf (Ops.foldableNotElem value) 1+ , bench "find" $ nf (Ops.foldableFind value) 1+ , bench "all" $ nf (Ops.foldableAll value) 1+ , bench "any" $ nf (Ops.foldableAny value) 1+ , bench "and" $ nf (Ops.foldableAnd value) 1+ , bench "or" $ nf (Ops.foldableOr value) 1++ -- Note: minimumBy/maximumBy do not work in constant memory they are in+ -- the O(n) group of benchmarks down below in this file.++ -- Applicative and Traversable operations+ -- TBD: traverse_+ , benchIOSink1 "mapM_" (Ops.foldableMapM_ value)+ -- TBD: for_+ -- TBD: forM_+ , benchIOSink1 "sequence_" (Ops.foldableSequence_ value)+ -- TBD: sequenceA_+ -- TBD: asum+ -- , benchIOSink1 "msum" (Ops.foldableMsum value)+ ] , bgroup "generation" [ -- Most basic, barely stream continuations running- benchIOSrc serially "unfoldr" Ops.sourceUnfoldr- , benchIOSrc serially "unfoldrM" Ops.sourceUnfoldrM- , benchIOSrc serially "intFromTo" Ops.sourceIntFromTo- , benchIOSrc serially "intFromThenTo" Ops.sourceIntFromThenTo- , benchIOSrc serially "integerFromStep" Ops.sourceIntegerFromStep- , benchIOSrc serially "fracFromThenTo" Ops.sourceFracFromThenTo- , benchIOSrc serially "fracFromTo" Ops.sourceFracFromTo- , benchIOSrc serially "fromList" Ops.sourceFromList- , benchIOSrc serially "fromListM" Ops.sourceFromListM+ benchIOSrc serially "unfoldr" (Ops.sourceUnfoldr value)+ , benchIOSrc serially "unfoldrM" (Ops.sourceUnfoldrM value)+ , benchIOSrc serially "intFromTo" (Ops.sourceIntFromTo value)+ , benchIOSrc serially "intFromThenTo" (Ops.sourceIntFromThenTo value)+ , benchIOSrc serially "integerFromStep" (Ops.sourceIntegerFromStep value)+ , benchIOSrc serially "fracFromThenTo" (Ops.sourceFracFromThenTo value)+ , benchIOSrc serially "fracFromTo" (Ops.sourceFracFromTo value)+ , benchIOSrc serially "fromList" (Ops.sourceFromList value)+ , benchIOSrc serially "fromListM" (Ops.sourceFromListM value) -- These are essentially cons and consM- , benchIOSrc serially "fromFoldable" Ops.sourceFromFoldable- , benchIOSrc serially "fromFoldableM" Ops.sourceFromFoldableM+ , benchIOSrc serially "fromFoldable" (Ops.sourceFromFoldable value)+ , benchIOSrc serially "fromFoldableM" (Ops.sourceFromFoldableM value)+ , benchIOSrc serially "currentTime/0.00001s"+ $ Ops.currentTime value 0.00001 ] , bgroup "elimination" [ bgroup "reduce" [ bgroup "IO"- [ benchIOSink "foldrM" Ops.foldrMReduce- , benchIOSink "foldl'" Ops.foldl'Reduce- , benchIOSink "foldl1'" Ops.foldl1'Reduce- , benchIOSink "foldlM'" Ops.foldlM'Reduce+ [+ benchIOSink value "foldl'" Ops.foldl'Reduce+ , benchIOSink value "foldl1'" Ops.foldl1'Reduce+ , benchIOSink value "foldlM'" Ops.foldlM'Reduce ] , bgroup "Identity"- [ benchIdentitySink "foldrM" Ops.foldrMReduce- , benchIdentitySink "foldl'" Ops.foldl'Reduce- , benchIdentitySink "foldl1'" Ops.foldl1'Reduce- , benchIdentitySink "foldlM'" Ops.foldlM'Reduce+ [+ benchIdentitySink value "foldl'" Ops.foldl'Reduce+ , benchIdentitySink value "foldl1'" Ops.foldl1'Reduce+ , benchIdentitySink value "foldlM'" Ops.foldlM'Reduce ] ] , bgroup "build"- [ bgroup "IO"- [ benchIOSink "foldrM" Ops.foldrMBuild- , benchIOSink "foldl'" Ops.foldl'Build- , benchIOSink "foldlM'" Ops.foldlM'Build- ]- , bgroup "Identity"- [ benchIdentitySink "foldrM" Ops.foldrMBuild- , benchIdentitySink "foldl'" Ops.foldl'Build- , benchIdentitySink "foldlM'" Ops.foldlM'Build+ [ bgroup "Identity"+ [ benchIdentitySink value "foldrM" Ops.foldrMBuild ] ]- , benchIOSink "uncons" Ops.uncons- , benchIOSink "toNull" $ Ops.toNull serially- , benchIOSink "mapM_" Ops.mapM_+ , benchIOSink value "uncons" Ops.uncons+ , benchIOSink value "toNull" $ Ops.toNull serially+ , benchIOSink value "mapM_" Ops.mapM_ - , benchIOSink "init" Ops.init- , benchIOSink "tail" Ops.tail- , benchIOSink "nullHeadTail" Ops.nullHeadTail+ , benchIOSink value "init" Ops.init -- this is too low and causes all benchmarks reported in ns- -- , benchIOSink "head" Ops.head- , benchIOSink "last" Ops.last- -- , benchIOSink "lookup" Ops.lookup- , benchIOSink "find" Ops.find- , benchIOSink "findIndex" Ops.findIndex- , benchIOSink "elemIndex" Ops.elemIndex+ -- , benchIOSink value "head" Ops.head+ , benchIOSink value "last" Ops.last+ -- , benchIOSink value "lookup" Ops.lookup+ , benchIOSink value "find" (Ops.find value)+ , benchIOSink value "findIndex" (Ops.findIndex value)+ , benchIOSink value "elemIndex" (Ops.elemIndex value) -- this is too low and causes all benchmarks reported in ns- -- , benchIOSink "null" Ops.null- , benchIOSink "elem" Ops.elem- , benchIOSink "notElem" Ops.notElem- , benchIOSink "all" Ops.all- , benchIOSink "any" Ops.any- , benchIOSink "and" Ops.and- , benchIOSink "or" Ops.or+ -- , benchIOSink value "null" Ops.null+ , benchIOSink value "elem" (Ops.elem value)+ , benchIOSink value "notElem" (Ops.notElem value)+ , benchIOSink value "all" (Ops.all value)+ , benchIOSink value "any" (Ops.any value)+ , benchIOSink value "and" (Ops.and value)+ , benchIOSink value "or" (Ops.or value) - , benchIOSink "length" Ops.length- , benchHoistSink "length . generally" (Ops.length . IP.generally)- , benchIOSink "sum" Ops.sum- , benchIOSink "product" Ops.product+ , benchIOSink value "length" Ops.length+ , benchHoistSink value "length . generally" (Ops.length . IP.generally)+ , benchIOSink value "sum" Ops.sum+ , benchIOSink value "product" Ops.product - , benchIOSink "maximumBy" Ops.maximumBy- , benchIOSink "maximum" Ops.maximum- , benchIOSink "minimumBy" Ops.minimumBy- , benchIOSink "minimum" Ops.minimum+ , benchIOSink value "maximumBy" Ops.maximumBy+ , benchIOSink value "maximum" Ops.maximum+ , benchIOSink value "minimumBy" Ops.minimumBy+ , benchIOSink value "minimum" Ops.minimum - , benchIOSink "toList" Ops.toList- , benchIOSink "toListRev" Ops.toListRev ] , bgroup "folds"- [ benchIOSink "drain" (S.fold FL.drain)- , benchIOSink "sink" (S.fold $ Sink.toFold Sink.drain)- , benchIOSink "last" (S.fold FL.last)- , benchIOSink "length" (S.fold FL.length)- , benchIOSink "sum" (S.fold FL.sum)- , benchIOSink "product" (S.fold FL.product)- , benchIOSink "maximumBy" (S.fold (FL.maximumBy compare))- , benchIOSink "maximum" (S.fold FL.maximum)- , benchIOSink "minimumBy" (S.fold (FL.minimumBy compare))- , benchIOSink "minimum" (S.fold FL.minimum)- , benchIOSink "mean" (\s -> S.fold FL.mean (S.map (fromIntegral :: Int -> Double) s))- , benchIOSink "variance" (\s -> S.fold FL.variance (S.map (fromIntegral :: Int -> Double) s))- , benchIOSink "stdDev" (\s -> S.fold FL.stdDev (S.map (fromIntegral :: Int -> Double) s))-- , benchIOSink "mconcat" (S.fold FL.mconcat . (S.map (Last . Just)))- , benchIOSink "foldMap" (S.fold (FL.foldMap (Last . Just)))+ [ benchIOSink value "drain" (S.fold FL.drain)+ , benchIOSink value "drainN" (S.fold (IFL.drainN value))+ , benchIOSink value "drainWhileTrue" (S.fold (IFL.drainWhile $ (<=) (value + 1)))+ , benchIOSink value "drainWhileFalse" (S.fold (IFL.drainWhile $ (>=) (value + 1)))+ , benchIOSink value "sink" (S.fold $ Sink.toFold Sink.drain)+ , benchIOSink value "last" (S.fold FL.last)+ , benchIOSink value "lastN.1" (S.fold (IA.lastN 1))+ , benchIOSink value "lastN.10" (S.fold (IA.lastN 10))+ , benchIOSink value "length" (S.fold FL.length)+ , benchIOSink value "sum" (S.fold FL.sum)+ , benchIOSink value "product" (S.fold FL.product)+ , benchIOSink value "maximumBy" (S.fold (FL.maximumBy compare))+ , benchIOSink value "maximum" (S.fold FL.maximum)+ , benchIOSink value "minimumBy" (S.fold (FL.minimumBy compare))+ , benchIOSink value "minimum" (S.fold FL.minimum)+ , benchIOSink value "mean" (\s -> S.fold FL.mean (S.map (fromIntegral :: Int -> Double) s))+ , benchIOSink value "variance" (\s -> S.fold FL.variance (S.map (fromIntegral :: Int -> Double) s))+ , benchIOSink value "stdDev" (\s -> S.fold FL.stdDev (S.map (fromIntegral :: Int -> Double) s)) - , benchIOSink "toList" (S.fold FL.toList)- , benchIOSink "toListRevF" (S.fold IFL.toListRevF)- , benchIOSink "toStream" (S.fold IP.toStream)- , benchIOSink "toStreamRev" (S.fold IP.toStreamRev)- , benchIOSink "writeN" (S.fold (A.writeN Ops.value))+ , benchIOSink value "mconcat" (S.fold FL.mconcat . (S.map (Last . Just)))+ , benchIOSink value "foldMap" (S.fold (FL.foldMap (Last . Just))) - , benchIOSink "index" (S.fold (FL.index Ops.maxValue))- , benchIOSink "head" (S.fold FL.head)- , benchIOSink "find" (S.fold (FL.find (== Ops.maxValue)))- , benchIOSink "findIndex" (S.fold (FL.findIndex (== Ops.maxValue)))- , benchIOSink "elemIndex" (S.fold (FL.elemIndex Ops.maxValue))+ , benchIOSink value "index" (S.fold (FL.index (value + 1)))+ , benchIOSink value "head" (S.fold FL.head)+ , benchIOSink value "find" (S.fold (FL.find (== (value + 1))))+ , benchIOSink value "findIndex" (S.fold (FL.findIndex (== (value + 1))))+ , benchIOSink value "elemIndex" (S.fold (FL.elemIndex (value + 1))) - , benchIOSink "null" (S.fold FL.null)- , benchIOSink "elem" (S.fold (FL.elem Ops.maxValue))- , benchIOSink "notElem" (S.fold (FL.notElem Ops.maxValue))- , benchIOSink "all" (S.fold (FL.all (<= Ops.maxValue)))- , benchIOSink "any" (S.fold (FL.any (> Ops.maxValue)))- , benchIOSink "and" (\s -> S.fold FL.and (S.map (<= Ops.maxValue) s))- , benchIOSink "or" (\s -> S.fold FL.or (S.map (> Ops.maxValue) s))+ , benchIOSink value "null" (S.fold FL.null)+ , benchIOSink value "elem" (S.fold (FL.elem (value + 1)))+ , benchIOSink value "notElem" (S.fold (FL.notElem (value + 1)))+ , benchIOSink value "all" (S.fold (FL.all (<= (value + 1))))+ , benchIOSink value "any" (S.fold (FL.any (> (value + 1))))+ , benchIOSink value "and" (\s -> S.fold FL.and (S.map (<= (value + 1)) s))+ , benchIOSink value "or" (\s -> S.fold FL.or (S.map (> (value + 1)) s)) ] , bgroup "fold-multi-stream"- [ benchIOSink1 "eqBy" Ops.eqBy- , benchIOSink1 "cmpBy" Ops.cmpBy- , benchIOSink "isPrefixOf" Ops.isPrefixOf- , benchIOSink "isSubsequenceOf" Ops.isSubsequenceOf- , benchIOSink "stripPrefix" Ops.stripPrefix+ [ benchIOSink1 "eqBy" (Ops.eqBy value)+ , benchIOSink1 "cmpBy" (Ops.cmpBy value)+ , benchIOSink value "isPrefixOf" Ops.isPrefixOf+ , benchIOSink value "isSubsequenceOf" Ops.isSubsequenceOf+ , benchIOSink value "stripPrefix" Ops.stripPrefix ] , bgroup "folds-transforms"- [ benchIOSink "drain" (S.fold FL.drain)- , benchIOSink "lmap" (S.fold (IFL.lmap (+1) FL.drain))- , benchIOSink "pipe-mapM"+ [ benchIOSink value "drain" (S.fold FL.drain)+ , benchIOSink value "lmap" (S.fold (IFL.lmap (+1) FL.drain))+ , benchIOSink value "pipe-mapM" (S.fold (IFL.transform (Pipe.mapM (\x -> return $ x + 1)) FL.drain)) ] , bgroup "folds-compositions" -- Applicative [- benchIOSink "all,any" (S.fold ((,) <$> FL.all (<= Ops.maxValue)- <*> FL.any (> Ops.maxValue)))- , benchIOSink "sum,length" (S.fold ((,) <$> FL.sum <*> FL.length))+ benchIOSink value "all,any" (S.fold ((,) <$> FL.all (<= (value + 1))+ <*> FL.any (> (value + 1))))+ , benchIOSink value "sum,length" (S.fold ((,) <$> FL.sum <*> FL.length)) ] , bgroup "pipes"- [ benchIOSink "mapM" (Ops.transformMapM serially 1)- , benchIOSink "compose" (Ops.transformComposeMapM serially 1)- , benchIOSink "tee" (Ops.transformTeeMapM serially 1)- , benchIOSink "zip" (Ops.transformZipMapM serially 1)+ [ benchIOSink value "mapM" (Ops.transformMapM serially 1)+ , benchIOSink value "compose" (Ops.transformComposeMapM serially 1)+ , benchIOSink value "tee" (Ops.transformTeeMapM serially 1)+ , benchIOSink value "zip" (Ops.transformZipMapM serially 1) ] , bgroup "pipesX4"- [ benchIOSink "mapM" (Ops.transformMapM serially 4)- , benchIOSink "compose" (Ops.transformComposeMapM serially 4)- , benchIOSink "tee" (Ops.transformTeeMapM serially 4)- , benchIOSink "zip" (Ops.transformZipMapM serially 4)+ [ benchIOSink value "mapM" (Ops.transformMapM serially 4)+ , benchIOSink value "compose" (Ops.transformComposeMapM serially 4)+ , benchIOSink value "tee" (Ops.transformTeeMapM serially 4)+ , benchIOSink value "zip" (Ops.transformZipMapM serially 4) ] , bgroup "transformer"- [ benchIOSrc serially "evalState" Ops.evalStateT- , benchIOSrc serially "withState" Ops.withState+ [ benchIOSrc serially "evalState" (Ops.evalStateT value)+ , benchIOSrc serially "withState" (Ops.withState value) ] , bgroup "transformation"- [ benchIOSink "scanl" (Ops.scan 1)- , benchIOSink "scanl1'" (Ops.scanl1' 1)- , benchIOSink "map" (Ops.map 1)- , benchIOSink "fmap" (Ops.fmap 1)- , benchIOSink "mapM" (Ops.mapM serially 1)- , benchIOSink "mapMaybe" (Ops.mapMaybe 1)- , benchIOSink "mapMaybeM" (Ops.mapMaybeM 1)+ [ benchIOSink value "scanl" (Ops.scan 1)+ , benchIOSink value "scanl1'" (Ops.scanl1' 1)+ , benchIOSink value "map" (Ops.map 1)+ , benchIOSink value "fmap" (Ops.fmap 1)+ , benchIOSink value "mapM" (Ops.mapM serially 1)+ , benchIOSink value "mapMaybe" (Ops.mapMaybe 1)+ , benchIOSink value "mapMaybeM" (Ops.mapMaybeM 1) , bench "sequence" $ nfIO $ randomRIO (1,1000) >>= \n ->- Ops.sequence serially (Ops.sourceUnfoldrMAction n)- , benchIOSink "findIndices" (Ops.findIndices 1)- , benchIOSink "elemIndices" (Ops.elemIndices 1)- , benchIOSink "reverse" (Ops.reverse 1)- , benchIOSink "reverse'" (Ops.reverse' 1)- , benchIOSink "foldrS" (Ops.foldrS 1)- , benchIOSink "foldrSMap" (Ops.foldrSMap 1)- , benchIOSink "foldrT" (Ops.foldrT 1)- , benchIOSink "foldrTMap" (Ops.foldrTMap 1)+ Ops.sequence serially (Ops.sourceUnfoldrMAction value n)+ , benchIOSink value "findIndices" (Ops.findIndices value 1)+ , benchIOSink value "elemIndices" (Ops.elemIndices value 1)+ , benchIOSink value "foldrS" (Ops.foldrS 1)+ , benchIOSink value "foldrSMap" (Ops.foldrSMap 1)+ , benchIOSink value "foldrT" (Ops.foldrT 1)+ , benchIOSink value "foldrTMap" (Ops.foldrTMap 1)+ , benchIOSink value "tap" (Ops.tap 1)+ , benchIOSink value "tapRate 1 second" (Ops.tapRate 1)+ , benchIOSink value "pollCounts 1 second" (Ops.pollCounts 1)+ , benchIOSink value "tapAsync" (Ops.tapAsync 1)+ , benchIOSink value "tapAsyncS" (Ops.tapAsyncS 1) ] , bgroup "transformationX4"- [ benchIOSink "scan" (Ops.scan 4)- , benchIOSink "scanl1'" (Ops.scanl1' 4)- , benchIOSink "map" (Ops.map 4)- , benchIOSink "fmap" (Ops.fmap 4)- , benchIOSink "mapM" (Ops.mapM serially 4)- , benchIOSink "mapMaybe" (Ops.mapMaybe 4)- , benchIOSink "mapMaybeM" (Ops.mapMaybeM 4)+ [ benchIOSink value "scan" (Ops.scan 4)+ , benchIOSink value "scanl1'" (Ops.scanl1' 4)+ , benchIOSink value "map" (Ops.map 4)+ , benchIOSink value "fmap" (Ops.fmap 4)+ , benchIOSink value "mapM" (Ops.mapM serially 4)+ , benchIOSink value "mapMaybe" (Ops.mapMaybe 4)+ , benchIOSink value "mapMaybeM" (Ops.mapMaybeM 4) -- , bench "sequence" $ nfIO $ randomRIO (1,1000) >>= \n -> -- Ops.sequence serially (Ops.sourceUnfoldrMAction n)- , benchIOSink "findIndices" (Ops.findIndices 4)- , benchIOSink "elemIndices" (Ops.elemIndices 4)+ , benchIOSink value "findIndices" (Ops.findIndices value 4)+ , benchIOSink value "elemIndices" (Ops.elemIndices value 4) ] , bgroup "filtering"- [ benchIOSink "filter-even" (Ops.filterEven 1)- , benchIOSink "filter-all-out" (Ops.filterAllOut 1)- , benchIOSink "filter-all-in" (Ops.filterAllIn 1)- , benchIOSink "take-all" (Ops.takeAll 1)- , benchIOSink "takeWhile-true" (Ops.takeWhileTrue 1)- --, benchIOSink "takeWhileM-true" (Ops.takeWhileMTrue 1)- , benchIOSink "drop-one" (Ops.dropOne 1)- , benchIOSink "drop-all" (Ops.dropAll 1)- , benchIOSink "dropWhile-true" (Ops.dropWhileTrue 1)- --, benchIOSink "dropWhileM-true" (Ops.dropWhileMTrue 1)- , benchIOSink "dropWhile-false" (Ops.dropWhileFalse 1)- , benchIOSink "deleteBy" (Ops.deleteBy 1)- , benchIOSink "intersperse" (Ops.intersperse 1)- , benchIOSink "insertBy" (Ops.insertBy 1)+ [ benchIOSink value "filter-even" (Ops.filterEven 1)+ , benchIOSink value "filter-all-out" (Ops.filterAllOut value 1)+ , benchIOSink value "filter-all-in" (Ops.filterAllIn value 1)++ , benchIOSink value "take-all" (Ops.takeAll value 1)+ , benchIOSink value "takeByTime-all"+ (Ops.takeByTime (NanoSecond64 maxBound) 1)+ , benchIOSink value "takeWhile-true" (Ops.takeWhileTrue value 1)+ --, benchIOSink value "takeWhileM-true" (Ops.takeWhileMTrue 1)++ -- "drop-one" is dual to "last"+ , benchIOSink value "drop-one" (Ops.dropOne 1)+ , benchIOSink value "drop-all" (Ops.dropAll value 1)+ , benchIOSink value "dropByTime-all"+ (Ops.dropByTime (NanoSecond64 maxBound) 1)+ , benchIOSink value "dropWhile-true" (Ops.dropWhileTrue value 1)+ --, benchIOSink value "dropWhileM-true" (Ops.dropWhileMTrue 1)+ , benchIOSink value "dropWhile-false" (Ops.dropWhileFalse value 1)++ , benchIOSink value "deleteBy" (Ops.deleteBy value 1)+ , benchIOSink value "intersperse" (Ops.intersperse value 1)+ , benchIOSink value "insertBy" (Ops.insertBy value 1) ] , bgroup "filteringX4"- [ benchIOSink "filter-even" (Ops.filterEven 4)- , benchIOSink "filter-all-out" (Ops.filterAllOut 4)- , benchIOSink "filter-all-in" (Ops.filterAllIn 4)- , benchIOSink "take-all" (Ops.takeAll 4)- , benchIOSink "takeWhile-true" (Ops.takeWhileTrue 4)- --, benchIOSink "takeWhileM-true" (Ops.takeWhileMTrue 4)- , benchIOSink "drop-one" (Ops.dropOne 4)- , benchIOSink "drop-all" (Ops.dropAll 4)- , benchIOSink "dropWhile-true" (Ops.dropWhileTrue 4)- --, benchIOSink "dropWhileM-true" (Ops.dropWhileMTrue 4)- , benchIOSink "dropWhile-false" (Ops.dropWhileFalse 4)- , benchIOSink "deleteBy" (Ops.deleteBy 4)- , benchIOSink "intersperse" (Ops.intersperse 4)- , benchIOSink "insertBy" (Ops.insertBy 4)+ [ benchIOSink value "filter-even" (Ops.filterEven 4)+ , benchIOSink value "filter-all-out" (Ops.filterAllOut value 4)+ , benchIOSink value "filter-all-in" (Ops.filterAllIn value 4)+ , benchIOSink value "take-all" (Ops.takeAll value 4)+ , benchIOSink value "takeWhile-true" (Ops.takeWhileTrue value 4)+ --, benchIOSink value "takeWhileM-true" (Ops.takeWhileMTrue 4)+ , benchIOSink value "drop-one" (Ops.dropOne 4)+ , benchIOSink value "drop-all" (Ops.dropAll value 4)+ , benchIOSink value "dropWhile-true" (Ops.dropWhileTrue value 4)+ --, benchIOSink value "dropWhileM-true" (Ops.dropWhileMTrue 4)+ , benchIOSink value "dropWhile-false" (Ops.dropWhileFalse value 4)+ , benchIOSink value "deleteBy" (Ops.deleteBy value 4)+ , benchIOSink value "intersperse" (Ops.intersperse value 4)+ , benchIOSink value "insertBy" (Ops.insertBy value 4) ] , bgroup "joining"- [ benchIOSrc1 "zip (2x50K)" (Ops.zip 50000)- , benchIOSrc1 "zipM (2x50K)" (Ops.zipM 50000)- , benchIOSrc1 "mergeBy (2x50K)" (Ops.mergeBy 50000)- , benchIOSrc1 "serial (2x50K)" (Ops.serial2 50000)- , benchIOSrc1 "append (2x50K)" (Ops.append2 50000)- , benchIOSrc1 "serial (2x2x25K)" (Ops.serial4 25000)- , benchIOSrc1 "append (2x2x25K)" (Ops.append4 25000)- , benchIOSrc1 "wSerial (2x50K)" Ops.wSerial2- , benchIOSrc1 "interleave (2x50K)" Ops.interleave2- , benchIOSrc1 "roundRobin (2x50K)" Ops.roundRobin2+ [ benchIOSrc1 "zip (2,x/2)" (Ops.zip (value `div` 2))+ , benchIOSrc1 "zipM (2,x/2)" (Ops.zipM (value `div` 2))+ , benchIOSrc1 "mergeBy (2,x/2)" (Ops.mergeBy (value `div` 2))+ , benchIOSrc1 "serial (2,x/2)" (Ops.serial2 (value `div` 2))+ , benchIOSrc1 "append (2,x/2)" (Ops.append2 (value `div` 2))+ , benchIOSrc1 "serial (2,2,x/4)" (Ops.serial4 (value `div` 4))+ , benchIOSrc1 "append (2,2,x/4)" (Ops.append4 (value `div` 4))+ , benchIOSrc1 "wSerial (2,x/2)" (Ops.wSerial2 value)+ , benchIOSrc1 "interleave (2,x/2)" (Ops.interleave2 value)+ , benchIOSrc1 "roundRobin (2,x/2)" (Ops.roundRobin2 value) ] , bgroup "concat-foldable"- [ benchIOSrc serially "foldMapWith (1x100K)" Ops.sourceFoldMapWith- , benchIOSrc serially "foldMapWithM (1x100K)" Ops.sourceFoldMapWithM- , benchIOSrc serially "foldMapM (1x100K)" Ops.sourceFoldMapM- , benchIOSrc serially "foldWithConcatMapId (1x100K)" Ops.sourceConcatMapId+ [ benchIOSrc serially "foldMapWith" (Ops.sourceFoldMapWith value)+ , benchIOSrc serially "foldMapWithM" (Ops.sourceFoldMapWithM value)+ , benchIOSrc serially "foldMapM" (Ops.sourceFoldMapM value)+ , benchIOSrc serially "foldWithConcatMapId" (Ops.sourceConcatMapId value) ] , bgroup "concat-serial"- [ benchIOSrc1 "concatMapPure (2x50K)" (Ops.concatMapPure 2 50000)- , benchIOSrc1 "concatMap (2x50K)" (Ops.concatMap 2 50000)- , benchIOSrc1 "concatMap (50Kx2)" (Ops.concatMap 50000 2)- , benchIOSrc1 "concatMapRepl (25Kx4)" Ops.concatMapRepl4xN- , benchIOSrc1 "concatUnfoldRepl (25Kx4)" Ops.concatUnfoldRepl4xN+ [ benchIOSrc1 "concatMapPure (2,x/2)" (Ops.concatMapPure 2 (value `div` 2))+ , benchIOSrc1 "concatMap (2,x/2)" (Ops.concatMap 2 (value `div` 2))+ , benchIOSrc1 "concatMap (x/2,2)" (Ops.concatMap (value `div` 2) 2)+ , benchIOSrc1 "concatMapRepl (x/4,4)" (Ops.concatMapRepl4xN value)+ , benchIOSrc1 "concatUnfoldRepl (x/4,4)" (Ops.concatUnfoldRepl4xN value) - , benchIOSrc1 "concatMapWithSerial (2x50K)"- (Ops.concatMapWithSerial 2 50000)- , benchIOSrc1 "concatMapWithSerial (50Kx2)"- (Ops.concatMapWithSerial 50000 2)+ , benchIOSrc1 "concatMapWithSerial (2,x/2)"+ (Ops.concatMapWithSerial 2 (value `div` 2))+ , benchIOSrc1 "concatMapWithSerial (x/2,2)"+ (Ops.concatMapWithSerial (value `div` 2) 2) - , benchIOSrc1 "concatMapWithAppend (2x50K)"- (Ops.concatMapWithAppend 2 50000)+ , benchIOSrc1 "concatMapWithAppend (2,x/2)"+ (Ops.concatMapWithAppend 2 (value `div` 2)) ] , bgroup "concat-interleave"- [ benchIOSrc1 "concatMapWithWSerial (2x50K)"- (Ops.concatMapWithWSerial 2 50000)- , benchIOSrc1 "concatMapWithWSerial (50Kx2)"- (Ops.concatMapWithWSerial 50000 2)- , benchIOSrc1 "concatUnfoldInterleaveRepl (25Kx4)"- Ops.concatUnfoldInterleaveRepl4xN- , benchIOSrc1 "concatUnfoldRoundrobinRepl (25Kx4)"- Ops.concatUnfoldRoundrobinRepl4xN+ [ benchIOSrc1 "concatMapWithWSerial (2,x/2)"+ (Ops.concatMapWithWSerial 2 (value `div` 2))+ , benchIOSrc1 "concatMapWithWSerial (x/2,2)"+ (Ops.concatMapWithWSerial (value `div` 2) 2) ] -- scanl-map and foldl-map are equivalent to the scan and fold in the foldl -- library. If scan/fold followed by a map is efficient enough we may not -- need monolithic implementations of these. , bgroup "mixed"- [ benchIOSink "scanl-map" (Ops.scanMap 1)- , benchIOSink "foldl-map" Ops.foldl'ReduceMap- , benchIOSink "sum-product-fold" Ops.sumProductFold- , benchIOSink "sum-product-scan" Ops.sumProductScan+ [ benchIOSink value "scanl-map" (Ops.scanMap 1)+ , benchIOSink value "foldl-map" Ops.foldl'ReduceMap+ , benchIOSink value "sum-product-fold" Ops.sumProductFold+ , benchIOSink value "sum-product-scan" Ops.sumProductScan ] , bgroup "mixedX4"- [ benchIOSink "scan-map" (Ops.scanMap 4)- , benchIOSink "drop-map" (Ops.dropMap 4)- , benchIOSink "drop-scan" (Ops.dropScan 4)- , benchIOSink "take-drop" (Ops.takeDrop 4)- , benchIOSink "take-scan" (Ops.takeScan 4)- , benchIOSink "take-map" (Ops.takeMap 4)- , benchIOSink "filter-drop" (Ops.filterDrop 4)- , benchIOSink "filter-take" (Ops.filterTake 4)- , benchIOSink "filter-scan" (Ops.filterScan 4)- , benchIOSink "filter-scanl1" (Ops.filterScanl1 4)- , benchIOSink "filter-map" (Ops.filterMap 4)+ [ benchIOSink value "scan-map" (Ops.scanMap 4)+ , benchIOSink value "drop-map" (Ops.dropMap 4)+ , benchIOSink value "drop-scan" (Ops.dropScan 4)+ , benchIOSink value "take-drop" (Ops.takeDrop value 4)+ , benchIOSink value "take-scan" (Ops.takeScan value 4)+ , benchIOSink value "take-map" (Ops.takeMap value 4)+ , benchIOSink value "filter-drop" (Ops.filterDrop value 4)+ , benchIOSink value "filter-take" (Ops.filterTake value 4)+ , benchIOSink value "filter-scan" (Ops.filterScan 4)+ , benchIOSink value "filter-scanl1" (Ops.filterScanl1 4)+ , benchIOSink value "filter-map" (Ops.filterMap value 4) ]+ ]+ , bgroup "wSerially"+ [ bgroup "transformation"+ [ benchIOSink value "fmap" $ Ops.fmap' wSerially 1+ ]+ ]+ , bgroup "zipSerially"+ [ bgroup "transformation"+ [ benchIOSink value "fmap" $ Ops.fmap' zipSerially 1+ ]+ ]+ -- Non-streaming operations. We keep these in a spearate group so that we+ -- can run these conveniently with smaller stream size.+ --+ -- These are also the operations that programmers should be aware of and+ -- should avoid using in a streaming application.++ -- XXX stack dominant (upto 1M), segregate? , bgroup "iterated" [ benchIOSrc serially "mapM" Ops.iterateMapM , benchIOSrc serially "scan(1/100)" Ops.iterateScan , benchIOSrc serially "scanl1(1/100)" Ops.iterateScanl1 , benchIOSrc serially "filterEven" Ops.iterateFilterEven- , benchIOSrc serially "takeAll" Ops.iterateTakeAll+ , benchIOSrc serially "takeAll" (Ops.iterateTakeAll value) , benchIOSrc serially "dropOne" Ops.iterateDropOne- , benchIOSrc serially "dropWhileFalse" Ops.iterateDropWhileFalse- , benchIOSrc serially "dropWhileTrue" Ops.iterateDropWhileTrue+ , benchIOSrc serially "dropWhileFalse" (Ops.iterateDropWhileFalse value)+ , benchIOSrc serially "dropWhileTrue" (Ops.iterateDropWhileTrue value) ]+ , bgroup "buffered"+ [ -- Inherently non-streaming operations++ -- Right folds for reducing are inherently non-streaming as the+ -- expression needs to be fully built before it can be reduced.+ -- XXX Stack dominant (up to 4MB), segregate?+ benchIOSink bufValue "foldrM/reduce/IO" Ops.foldrMReduce+ , benchIdentitySink bufValue "foldrM/reduce/Identity" Ops.foldrMReduce++ -- Left folds for building a structure are inherently non-streaming as+ -- the structure cannot be lazily consumed until fully built.+ , benchIOSink bufValue "foldl'/build/IO" Ops.foldl'Build+ , benchIdentitySink bufValue "foldl'/build/Identity" Ops.foldl'Build+ , benchIOSink bufValue "foldlM'/build/IO" Ops.foldlM'Build+ , benchIdentitySink bufValue "foldlM'/build/Identity" Ops.foldlM'Build++ -- accumulation due to strictness of IO monad+ -- XXX Stack dominant, segregate?+ , benchIOSink bufValue "foldrM/build/IO" Ops.foldrMBuild+ , benchPureSinkIO bufValue "traversable/mapM" Ops.traversableMapM++ -- Converting the stream to a list or pure stream+ -- XXX Stack dominant, segregate?+ , benchIOSink bufValue "toList" Ops.toList+ , benchIOSink bufValue "toListRev" Ops.toListRev++ , benchIOSink bufValue "toStream" (S.fold IP.toStream)+ , benchIOSink bufValue "toStreamRev" (S.fold IP.toStreamRev)++ , benchIOSink bufValue "folds/toList" (S.fold FL.toList)+ , benchIOSink bufValue "folds/toListRevF" (S.fold IFL.toListRevF)++ -- Converting the stream to an array+ , benchIOSink bufValue "folds/lastN.Max" (S.fold (IA.lastN (bufValue + 1)))+ , benchIOSink bufValue "folds/writeN" (S.fold (A.writeN bufValue))++ -- Reversing/sorting a stream+ , benchIOSink bufValue "reverse" (Ops.reverse 1)+ , benchIOSink bufValue "reverse'" (Ops.reverse' 1)++ -- XXX the definitions of minimumBy and maximumBy in Data.Foldable use+ -- foldl1 which does not work in constant memory for our implementation.+ -- It works in constant memory for lists but even for lists it takes 15x+ -- more time compared to our foldl' based implementation.+ , bench "minimumBy" $ nf (flip Ops.foldableMinBy 1) value+ , bench "maximumBy" $ nf (flip Ops.foldableMaxBy 1) value+ , bench "minimumByList" $ nf (flip Ops.foldableListMinBy 1) value++ -- XXX can these be streaming? Can we have special read/show style type+ -- classes supporting streaming?+ , mkString bufValue `deepseq` (bench "readsPrec pure streams" $+ nf Ops.readInstance (mkString bufValue))+ , mkString bufValue `deepseq` (bench "readsPrec Haskell lists" $+ nf Ops.readInstanceList (mkListString bufValue))+ , mkList bufValue `deepseq` (bench "showPrec Haskell lists" $+ nf Ops.showInstanceList (mkList bufValue))++ -- XXX streaming operations that can potentially be fixed++ -- XXX These consume a lot of stack, fix or segregate+ , benchIOSink bufValue "tail" Ops.tail+ , benchIOSink bufValue "nullHeadTail" Ops.nullHeadTail++ , benchIOSrc1 "concatUnfoldInterleaveRepl (x/4,4)"+ (Ops.concatUnfoldInterleaveRepl4xN bufValue)+ , benchIOSrc1 "concatUnfoldRoundrobinRepl (x/4,4)"+ (Ops.concatUnfoldRoundrobinRepl4xN bufValue)+ ]+ , bgroup "traversable"+ [ -- Traversable instance+ benchPureSinkIO bufValue "traverse" Ops.traversableTraverse+ , benchPureSinkIO bufValue "sequenceA" Ops.traversableSequenceA+ , benchPureSinkIO bufValue "mapM" Ops.traversableMapM+ , benchPureSinkIO bufValue "sequence" Ops.traversableSequence ] ]
benchmark/LinearAsync.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Main -- Copyright : (c) 2018 Harendra Kumar@@ -8,98 +9,139 @@ import Control.DeepSeq (NFData) -- import Data.Functor.Identity (Identity, runIdentity) import System.Random (randomRIO)-import qualified Streamly.Benchmark.Prelude as Ops +import Common (parseCLIOpts)+ import Streamly import Gauge +import qualified Streamly.Benchmark.Prelude as Ops+ -- We need a monadic bind here to make sure that the function f does not get -- completely optimized out by the compiler in some cases. -- -- | Takes a fold method, and uses it with a default source. {-# INLINE benchIO #-}-benchIO :: (IsStream t, NFData b) => String -> (t IO Int -> IO b) -> Benchmark-benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.source+benchIO :: (IsStream t, NFData b) => Int -> String -> (t IO Int -> IO b) -> Benchmark+benchIO value name f = bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.source value -- | Takes a source, and uses it with a default drain/fold method. {-# INLINE benchSrcIO #-} benchSrcIO- :: (t IO Int -> SerialT IO Int)+ :: (t IO a -> SerialT IO a) -> String- -> (Int -> t IO Int)+ -> (Int -> t IO a) -> Benchmark benchSrcIO t name f = bench name $ nfIO $ randomRIO (1,1) >>= Ops.toNull t . f +{-# INLINE benchMonadicSrcIO #-}+benchMonadicSrcIO :: String -> (Int -> IO ()) -> Benchmark+benchMonadicSrcIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f+ {- _benchId :: NFData b => String -> (Ops.Stream m Int -> Identity b) -> Benchmark _benchId name f = bench name $ nf (runIdentity . f) (Ops.source 10) -} +defaultStreamSize :: Int+defaultStreamSize = 100000+ main :: IO ()-main =- defaultMain+main = do+ -- XXX Fix indentation+ (value, cfg, benches) <- parseCLIOpts defaultStreamSize+ let value2 = round $ sqrt $ (fromIntegral value :: Double)+ value2 `seq` value `seq` runMode (mode cfg) cfg benches [ bgroup "asyncly"- [ benchSrcIO asyncly "unfoldr" Ops.sourceUnfoldr- , benchSrcIO asyncly "unfoldrM" Ops.sourceUnfoldrM- , benchSrcIO asyncly "fromFoldable" Ops.sourceFromFoldable- , benchSrcIO asyncly "fromFoldableM" Ops.sourceFromFoldableM- , benchSrcIO asyncly "foldMapWith" Ops.sourceFoldMapWith- , benchSrcIO asyncly "foldMapWithM" Ops.sourceFoldMapWithM- , benchSrcIO asyncly "foldMapM" Ops.sourceFoldMapM- , benchIO "map" $ Ops.map' asyncly 1- , benchIO "fmap" $ Ops.fmap' asyncly 1- , benchIO "mapM" $ Ops.mapM asyncly 1+ [ benchSrcIO asyncly "unfoldr" (Ops.sourceUnfoldr value)+ , benchSrcIO asyncly "unfoldrM" (Ops.sourceUnfoldrM value)+ , benchSrcIO asyncly "fromFoldable" (Ops.sourceFromFoldable value)+ , benchSrcIO asyncly "fromFoldableM" (Ops.sourceFromFoldableM value)+ , benchSrcIO asyncly "foldMapWith" (Ops.sourceFoldMapWith value)+ , benchSrcIO asyncly "foldMapWithM" (Ops.sourceFoldMapWithM value)+ , benchSrcIO asyncly "foldMapM" (Ops.sourceFoldMapM value)+ , benchIO value "map" $ Ops.map' asyncly 1+ , benchIO value "fmap" $ Ops.fmap' asyncly 1+ , benchIO value "mapM" $ Ops.mapM asyncly 1 , benchSrcIO asyncly "unfoldrM maxThreads 1"- (maxThreads 1 . Ops.sourceUnfoldrM)- , benchSrcIO asyncly "unfoldrM maxBuffer 1 (1000 ops)"- (maxBuffer 1 . Ops.sourceUnfoldrMN 1000)+ (maxThreads 1 . Ops.sourceUnfoldrM value)+ , benchSrcIO asyncly "unfoldrM maxBuffer 1 (x/10 ops)"+ (maxBuffer 1 . Ops.sourceUnfoldrMN (value `div` 10))+ , benchMonadicSrcIO "concatMapWith (2,x/2)"+ (Ops.concatStreamsWith async 2 (value `div` 2))+ , benchMonadicSrcIO "concatMapWith (sqrt x,sqrt x)"+ (Ops.concatStreamsWith async value2 value2)+ , benchMonadicSrcIO "concatMapWith (sqrt x * 2,sqrt x / 2)"+ (Ops.concatStreamsWith async (value2 * 2) (value2 `div` 2)) ] , bgroup "wAsyncly"- [ benchSrcIO wAsyncly "unfoldr" Ops.sourceUnfoldr- , benchSrcIO wAsyncly "unfoldrM" Ops.sourceUnfoldrM- , benchSrcIO wAsyncly "fromFoldable" Ops.sourceFromFoldable- , benchSrcIO wAsyncly "fromFoldableM" Ops.sourceFromFoldableM- , benchSrcIO wAsyncly "foldMapWith" Ops.sourceFoldMapWith- , benchSrcIO wAsyncly "foldMapWithM" Ops.sourceFoldMapWithM- , benchSrcIO wAsyncly "foldMapM" Ops.sourceFoldMapM- , benchIO "map" $ Ops.map' wAsyncly 1- , benchIO "fmap" $ Ops.fmap' wAsyncly 1- , benchIO "mapM" $ Ops.mapM wAsyncly 1+ [ benchSrcIO wAsyncly "unfoldr" (Ops.sourceUnfoldr value)+ , benchSrcIO wAsyncly "unfoldrM" (Ops.sourceUnfoldrM value)+ , benchSrcIO wAsyncly "fromFoldable" (Ops.sourceFromFoldable value)+ , benchSrcIO wAsyncly "fromFoldableM" (Ops.sourceFromFoldableM value)+ , benchSrcIO wAsyncly "foldMapWith" (Ops.sourceFoldMapWith value)+ , benchSrcIO wAsyncly "foldMapWithM" (Ops.sourceFoldMapWithM value)+ , benchSrcIO wAsyncly "foldMapM" (Ops.sourceFoldMapM value)+ , benchIO value "map" $ Ops.map' wAsyncly 1+ , benchIO value "fmap" $ Ops.fmap' wAsyncly 1+ , benchIO value "mapM" $ Ops.mapM wAsyncly 1+ , benchSrcIO wAsyncly "unfoldrM maxThreads 1"+ (maxThreads 1 . Ops.sourceUnfoldrM value)+ , benchSrcIO wAsyncly "unfoldrM maxBuffer 1 (x/10 ops)"+ (maxBuffer 1 . Ops.sourceUnfoldrMN (value `div` 10))+ -- When we merge streams using wAsync the size of the queue increases+ -- slowly because of the binary composition adding just one more item+ -- to the work queue only after every scheduling pass through the+ -- work queue.+ --+ -- We should see the memory consumption increasing slowly if these+ -- benchmarks are left to run on infinite number of streams of infinite+ -- sizes.+ , benchMonadicSrcIO "concatMapWith (2,x/2)"+ (Ops.concatStreamsWith wAsync 2 (value `div` 2))+ , benchMonadicSrcIO "concatMapWith (sqrt x,sqrt x)"+ (Ops.concatStreamsWith wAsync value2 value2)+ , benchMonadicSrcIO "concatMapWith (sqrt x * 2,sqrt x / 2)"+ (Ops.concatStreamsWith wAsync (value2 * 2) (value2 `div` 2)) ]- -- unfoldr and fromFoldable are always serial and thereofore the same for+ -- unfoldr and fromFoldable are always serial and therefore the same for -- all stream types. , bgroup "aheadly"- [ benchSrcIO aheadly "unfoldr" Ops.sourceUnfoldr- , benchSrcIO aheadly "unfoldrM" Ops.sourceUnfoldrM- , benchSrcIO aheadly "fromFoldableM" Ops.sourceFromFoldableM- -- , benchSrcIO aheadly "foldMapWith" Ops.sourceFoldMapWith- , benchSrcIO aheadly "foldMapWithM" Ops.sourceFoldMapWithM- , benchSrcIO aheadly "foldMapM" Ops.sourceFoldMapM- , benchIO "map" $ Ops.map' aheadly 1- , benchIO "fmap" $ Ops.fmap' aheadly 1- , benchIO "mapM" $ Ops.mapM aheadly 1+ [ benchSrcIO aheadly "unfoldr" (Ops.sourceUnfoldr value)+ , benchSrcIO aheadly "unfoldrM" (Ops.sourceUnfoldrM value)+ , benchSrcIO aheadly "fromFoldableM" (Ops.sourceFromFoldableM value)+ , benchSrcIO aheadly "foldMapWith" (Ops.sourceFoldMapWith value)+ , benchSrcIO aheadly "foldMapWithM" (Ops.sourceFoldMapWithM value)+ , benchSrcIO aheadly "foldMapM" (Ops.sourceFoldMapM value)+ , benchIO value "map" $ Ops.map' aheadly 1+ , benchIO value "fmap" $ Ops.fmap' aheadly 1+ , benchIO value "mapM" $ Ops.mapM aheadly 1 , benchSrcIO aheadly "unfoldrM maxThreads 1"- (maxThreads 1 . Ops.sourceUnfoldrM)- , benchSrcIO aheadly "unfoldrM maxBuffer 1 (1000 ops)"- (maxBuffer 1 . Ops.sourceUnfoldrMN 1000)- -- , benchSrcIO aheadly "fromFoldable" Ops.sourceFromFoldable+ (maxThreads 1 . Ops.sourceUnfoldrM value)+ , benchSrcIO aheadly "unfoldrM maxBuffer 1 (x/10 ops)"+ (maxBuffer 1 . Ops.sourceUnfoldrMN (value `div` 10))+ , benchSrcIO aheadly "fromFoldable" (Ops.sourceFromFoldable value)+ , benchMonadicSrcIO "concatMapWith (2,x/2)"+ (Ops.concatStreamsWith ahead 2 (value `div` 2))+ , benchMonadicSrcIO "concatMapWith (sqrt x,sqrt x)"+ (Ops.concatStreamsWith ahead value2 value2)+ , benchMonadicSrcIO "concatMapWith (sqrt x * 2,sqrt x / 2)"+ (Ops.concatStreamsWith ahead (value2 * 2) (value2 `div` 2)) ]- -- XXX need to use smaller streams to finish in reasonable time- , bgroup "parallely"- [ benchSrcIO parallely "unfoldr" Ops.sourceUnfoldr- , benchSrcIO parallely "unfoldrM" Ops.sourceUnfoldrM- --, benchSrcIO parallely "fromFoldable" Ops.sourceFromFoldable- , benchSrcIO parallely "fromFoldableM" Ops.sourceFromFoldableM- -- , benchSrcIO parallely "foldMapWith" Ops.sourceFoldMapWith- , benchSrcIO parallely "foldMapWithM" Ops.sourceFoldMapWithM- , benchSrcIO parallely "foldMapM" Ops.sourceFoldMapM- , benchIO "map" $ Ops.map' parallely 1- , benchIO "fmap" $ Ops.fmap' parallely 1- , benchIO "mapM" $ Ops.mapM parallely 1- -- Zip has only one parallel flavor- , benchIO "zip" Ops.zipAsync- , benchIO "zipM" Ops.zipAsyncM- , benchIO "zipAp" Ops.zipAsyncAp+ , bgroup "zip"+ [ benchSrcIO serially "zipAsync (2,x/2)" (Ops.zipAsync (value `div` 2))+ , benchSrcIO serially "zipAsyncM (2,x/2)"+ (Ops.zipAsyncM (value `div` 2))+ , benchSrcIO serially "zipAsyncAp (2,x/2)"+ (Ops.zipAsyncAp (value `div` 2))+ , benchIO value "fmap zipAsyncly" $ Ops.fmap' zipAsyncly 1+ , benchSrcIO serially "mergeAsyncBy (2,x/2)"+ (Ops.mergeAsyncBy (value `div` 2))+ , benchSrcIO serially "mergeAsyncByM (2,x/2)"+ (Ops.mergeAsyncByM (value `div` 2))+ -- Parallel stages in a pipeline+ , benchIO value "parAppMap" Ops.parAppMap+ , benchIO value "parAppSum" Ops.parAppSum ] ]
benchmark/LinearRate.hs view
@@ -10,11 +10,14 @@ -- import Data.Functor.Identity (Identity, runIdentity) import System.Random (randomRIO)-import qualified Streamly.Benchmark.Prelude as Ops +import Common (parseCLIOpts)+ import Streamly import Gauge +import qualified Streamly.Benchmark.Prelude as Ops+ -- | Takes a source, and uses it with a default drain/fold method. {-# INLINE benchSrcIO #-} benchSrcIO@@ -30,31 +33,36 @@ _benchId name f = bench name $ nf (runIdentity . f) (Ops.source 10) -} +defaultStreamSize :: Int+defaultStreamSize = 100000+ main :: IO ()-main =- defaultMain+main = do+ -- XXX Fix indentation+ (value, cfg, benches) <- parseCLIOpts defaultStreamSize+ value `seq` runMode (mode cfg) cfg benches -- XXX arbitrarily large rate should be the same as rate Nothing [ bgroup "avgrate" [ bgroup "asyncly" [ -- benchIO "unfoldr" $ Ops.toNull asyncly- benchSrcIO asyncly "unfoldrM" Ops.sourceUnfoldrM+ benchSrcIO asyncly "unfoldrM" (Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/Nothing"- (rate Nothing . Ops.sourceUnfoldrM)+ (rate Nothing . Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/1,000,000"- (avgRate 1000000 . Ops.sourceUnfoldrM)+ (avgRate 1000000 . Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/3,000,000"- (avgRate 3000000 . Ops.sourceUnfoldrM)+ (avgRate 3000000 . Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/10,000,000/maxThreads1"- (maxThreads 1 . avgRate 10000000 . Ops.sourceUnfoldrM)+ (maxThreads 1 . avgRate 10000000 . Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/10,000,000"- (avgRate 10000000 . Ops.sourceUnfoldrM)+ (avgRate 10000000 . Ops.sourceUnfoldrM value) , benchSrcIO asyncly "unfoldrM/20,000,000"- (avgRate 20000000 . Ops.sourceUnfoldrM)+ (avgRate 20000000 . Ops.sourceUnfoldrM value) ] , bgroup "aheadly" [ benchSrcIO aheadly "unfoldrM/1,000,000"- (avgRate 1000000 . Ops.sourceUnfoldrM)+ (avgRate 1000000 . Ops.sourceUnfoldrM value) ] ] ]
benchmark/NanoBenchmarks.hs view
@@ -15,7 +15,7 @@ import qualified Streamly.Data.Fold as FL import qualified Streamly.Internal.Prelude as Internal import qualified Streamly.Prelude as S-import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamK as K import Data.Char (ord) import Gauge@@ -114,7 +114,7 @@ defaultMain [mkBenchText "splitOn abc...xyz" inText $ do (S.length $ Internal.splitOnSeq (A.fromList $ map (fromIntegral . ord) "abcdefghijklmnopqrstuvwxyz") FL.drain- $ IFH.toStream inText) >>= print+ $ IFH.toBytes inText) >>= print ] where
benchmark/Nested.hs view
@@ -8,89 +8,54 @@ import Control.DeepSeq (NFData) import Data.Functor.Identity (Identity, runIdentity) import System.Random (randomRIO)-import qualified NestedOps as Ops++import Common (parseCLIOpts)+ import Streamly import Gauge +import qualified NestedOps as Ops+ benchIO :: (NFData b) => String -> (Int -> IO b) -> Benchmark benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f _benchId :: (NFData b) => String -> (Int -> Identity b) -> Benchmark _benchId name f = bench name $ nf (\g -> runIdentity (g 1)) f +defaultStreamSize :: Int+defaultStreamSize = 100000+ main :: IO ()-main =- -- TBD Study scaling with 10, 100, 1000 loop iterations- defaultMain+main = do+ -- XXX Fix indentation+ (linearCount, cfg, benches) <- parseCLIOpts defaultStreamSize+ linearCount `seq` runMode (mode cfg) cfg benches [ bgroup "serially"- [ benchIO "toNullAp" $ Ops.toNullAp serially- , benchIO "toNull" $ Ops.toNull serially- , benchIO "toNull3" $ Ops.toNull3 serially- , benchIO "toList" $ Ops.toList serially- -- , benchIO "toListSome" $ Ops.toListSome serially- , benchIO "filterAllOut" $ Ops.filterAllOut serially- , benchIO "filterAllIn" $ Ops.filterAllIn serially- , benchIO "filterSome" $ Ops.filterSome serially- , benchIO "breakAfterSome" $ Ops.breakAfterSome serially+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount serially+ , benchIO "toNull" $ Ops.toNull linearCount serially+ , benchIO "toNull3" $ Ops.toNull3 linearCount serially+ -- , benchIO "toList" $ Ops.toList linearCount serially+ -- XXX takes too much stack space+ , benchIO "toListSome" $ Ops.toListSome linearCount serially+ , benchIO "filterAllOut" $ Ops.filterAllOut linearCount serially+ , benchIO "filterAllIn" $ Ops.filterAllIn linearCount serially+ , benchIO "filterSome" $ Ops.filterSome linearCount serially+ , benchIO "breakAfterSome" $ Ops.breakAfterSome linearCount serially ] , bgroup "wSerially"- [ benchIO "toNullAp" $ Ops.toNullAp wSerially- , benchIO "toNull" $ Ops.toNull wSerially- , benchIO "toNull3" $ Ops.toNull3 wSerially- , benchIO "toList" $ Ops.toList wSerially- -- , benchIO "toListSome" $ Ops.toListSome wSerially- , benchIO "filterAllOut" $ Ops.filterAllOut wSerially- , benchIO "filterAllIn" $ Ops.filterAllIn wSerially- , benchIO "filterSome" $ Ops.filterSome wSerially- , benchIO "breakAfterSome" $ Ops.breakAfterSome wSerially- ]-- , bgroup "aheadly"- [ benchIO "toNullAp" $ Ops.toNullAp aheadly- , benchIO "toNull" $ Ops.toNull aheadly- , benchIO "toNull3" $ Ops.toNull3 aheadly- , benchIO "toList" $ Ops.toList aheadly- -- , benchIO "toListSome" $ Ops.toListSome aheadly- , benchIO "filterAllOut" $ Ops.filterAllOut aheadly- , benchIO "filterAllIn" $ Ops.filterAllIn aheadly- , benchIO "filterSome" $ Ops.filterSome aheadly- , benchIO "breakAfterSome" $ Ops.breakAfterSome aheadly- ]-- , bgroup "asyncly"- [ benchIO "toNullAp" $ Ops.toNullAp asyncly- , benchIO "toNull" $ Ops.toNull asyncly- , benchIO "toNull3" $ Ops.toNull3 asyncly- , benchIO "toList" $ Ops.toList asyncly- -- , benchIO "toListSome" $ Ops.toListSome asyncly- , benchIO "filterAllOut" $ Ops.filterAllOut asyncly- , benchIO "filterAllIn" $ Ops.filterAllIn asyncly- , benchIO "filterSome" $ Ops.filterSome asyncly- , benchIO "breakAfterSome" $ Ops.breakAfterSome asyncly- ]-- , bgroup "wAsyncly"- [ benchIO "toNullAp" $ Ops.toNullAp wAsyncly- , benchIO "toNull" $ Ops.toNull wAsyncly- , benchIO "toNull3" $ Ops.toNull3 wAsyncly- , benchIO "toList" $ Ops.toList wAsyncly- -- , benchIO "toListSome" $ Ops.toListSome wAsyncly- , benchIO "filterAllOut" $ Ops.filterAllOut wAsyncly- , benchIO "filterAllIn" $ Ops.filterAllIn wAsyncly- , benchIO "filterSome" $ Ops.filterSome wAsyncly- , benchIO "breakAfterSome" $ Ops.breakAfterSome wAsyncly+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount wSerially+ , benchIO "toNull" $ Ops.toNull linearCount wSerially+ , benchIO "toNull3" $ Ops.toNull3 linearCount wSerially+ -- , benchIO "toList" $ Ops.toList linearCount wSerially+ , benchIO "toListSome" $ Ops.toListSome linearCount wSerially+ , benchIO "filterAllOut" $ Ops.filterAllOut linearCount wSerially+ , benchIO "filterAllIn" $ Ops.filterAllIn linearCount wSerially+ , benchIO "filterSome" $ Ops.filterSome linearCount wSerially+ , benchIO "breakAfterSome" $ Ops.breakAfterSome linearCount wSerially ] - , bgroup "parallely"- [ benchIO "toNullAp" $ Ops.toNullAp parallely- , benchIO "toNull" $ Ops.toNull parallely- , benchIO "toNull3" $ Ops.toNull3 parallely- , benchIO "toList" $ Ops.toList parallely- --, benchIO "toListSome" $ Ops.toListSome parallely- , benchIO "filterAllOut" $ Ops.filterAllOut parallely- , benchIO "filterAllIn" $ Ops.filterAllIn parallely- , benchIO "filterSome" $ Ops.filterSome parallely- , benchIO "breakAfterSome" $ Ops.breakAfterSome parallely+ , bgroup "zipSerially"+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount zipSerially ] ]
+ benchmark/NestedConcurrent.hs view
@@ -0,0 +1,84 @@+-- |+-- Module : Main+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com++import Control.DeepSeq (NFData)+import Control.Monad (when)+import Data.Functor.Identity (Identity, runIdentity)+import System.Random (randomRIO)++import Common (parseCLIOpts)++import Streamly+import Gauge++import qualified NestedOps as Ops++benchIO :: (NFData b) => String -> (Int -> IO b) -> Benchmark+benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f++_benchId :: (NFData b) => String -> (Int -> Identity b) -> Benchmark+_benchId name f = bench name $ nf (\g -> runIdentity (g 1)) f++defaultStreamSize :: Int+defaultStreamSize = 100000++main :: IO ()+main = do+ -- XXX Fix indentation+ (linearCount, cfg, benches) <- parseCLIOpts defaultStreamSize+ let finiteCount = min linearCount defaultStreamSize+ when (finiteCount /= linearCount) $+ putStrLn $ "Limiting stream size to "+ ++ show defaultStreamSize+ ++ " for finite stream operations only"++ finiteCount `seq` linearCount `seq` runMode (mode cfg) cfg benches+ [+ bgroup "aheadly"+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount aheadly+ , benchIO "toNull" $ Ops.toNull linearCount aheadly+ , benchIO "toNull3" $ Ops.toNull3 linearCount aheadly+ -- , benchIO "toList" $ Ops.toList linearCount aheadly+ -- XXX consumes too much stack space+ , benchIO "toListSome" $ Ops.toListSome linearCount aheadly+ , benchIO "filterAllOut" $ Ops.filterAllOut linearCount aheadly+ , benchIO "filterAllIn" $ Ops.filterAllIn linearCount aheadly+ , benchIO "filterSome" $ Ops.filterSome linearCount aheadly+ , benchIO "breakAfterSome" $ Ops.breakAfterSome linearCount aheadly+ ]++ , bgroup "asyncly"+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount asyncly+ , benchIO "toNull" $ Ops.toNull linearCount asyncly+ , benchIO "toNull3" $ Ops.toNull3 linearCount asyncly+ -- , benchIO "toList" $ Ops.toList linearCount asyncly+ , benchIO "toListSome" $ Ops.toListSome linearCount asyncly+ , benchIO "filterAllOut" $ Ops.filterAllOut linearCount asyncly+ , benchIO "filterAllIn" $ Ops.filterAllIn linearCount asyncly+ , benchIO "filterSome" $ Ops.filterSome linearCount asyncly+ , benchIO "breakAfterSome" $ Ops.breakAfterSome linearCount asyncly+ ]++ , bgroup "zipAsyncly"+ [ benchIO "toNullAp" $ Ops.toNullAp linearCount zipAsyncly+ ]++ -- Operations that are not scalable to infinite streams+ , bgroup "finite"+ [ bgroup "wAsyncly"+ [ benchIO "toNullAp" $ Ops.toNullAp finiteCount wAsyncly+ , benchIO "toNull" $ Ops.toNull finiteCount wAsyncly+ , benchIO "toNull3" $ Ops.toNull3 finiteCount wAsyncly+ -- , benchIO "toList" $ Ops.toList finiteCount wAsyncly+ , benchIO "toListSome" $ Ops.toListSome finiteCount wAsyncly+ , benchIO "filterAllOut" $ Ops.filterAllOut finiteCount wAsyncly+ -- , benchIO "filterAllIn" $ Ops.filterAllIn finiteCount wAsyncly+ , benchIO "filterSome" $ Ops.filterSome finiteCount wAsyncly+ , benchIO "breakAfterSome" $ Ops.breakAfterSome finiteCount wAsyncly+ ]+ ]+ ]
benchmark/NestedOps.hs view
@@ -5,6 +5,7 @@ -- License : MIT -- Maintainer : streamly@composewell.com +{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -16,18 +17,6 @@ import qualified Streamly as S hiding (runStream) import qualified Streamly.Prelude as S -linearCount :: Int-linearCount = 100000---- double nested loop-nestedCount2 :: Int--- nestedCount2 = round (fromIntegral linearCount**(1/2::Double))-nestedCount2 = 100---- triple nested loop-nestedCount3 :: Int-nestedCount3 = round (fromIntegral linearCount**(1/3::Double))- ------------------------------------------------------------------------------- -- Stream generation and elimination -------------------------------------------------------------------------------@@ -38,6 +27,7 @@ source :: (S.MonadAsync m, S.IsStream t) => Int -> Int -> t m Int source = sourceUnfoldrM +-- Change this to "sourceUnfoldrM value n" for consistency {-# INLINE sourceUnfoldrM #-} sourceUnfoldrM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int sourceUnfoldrM n value = S.serially $ S.unfoldrM step n@@ -70,90 +60,108 @@ {-# INLINE toNullAp #-} toNullAp- :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-toNullAp t start = runStream . t $+ :: (S.IsStream t, S.MonadAsync m, Applicative (t m))+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+toNullAp linearCount t start = runStream . t $ (+) <$> source start nestedCount2 <*> source start nestedCount2+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE toNull #-} toNull :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-toNull t start = runStream . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+toNull linearCount t start = runStream . t $ do x <- source start nestedCount2 y <- source start nestedCount2 return $ x + y+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE toNull3 #-} toNull3 :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-toNull3 t start = runStream . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+toNull3 linearCount t start = runStream . t $ do x <- source start nestedCount3 y <- source start nestedCount3 z <- source start nestedCount3 return $ x + y + z+ where+ nestedCount3 = round (fromIntegral linearCount**(1/3::Double)) {-# INLINE toList #-} toList :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m [Int]-toList t start = runToList . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m [Int]+toList linearCount t start = runToList . t $ do x <- source start nestedCount2 y <- source start nestedCount2 return $ x + y+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) +-- Taking a specified number of elements is very expensive in logict so we have+-- a test to measure the same. {-# INLINE toListSome #-} toListSome :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m [Int]-toListSome t start =- runToList . t $ S.take 1000 $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m [Int]+toListSome linearCount t start =+ runToList . t $ S.take 10000 $ do x <- source start nestedCount2 y <- source start nestedCount2 return $ x + y+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE filterAllOut #-} filterAllOut :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-filterAllOut t start = runStream . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+filterAllOut linearCount t start = runStream . t $ do x <- source start nestedCount2 y <- source start nestedCount2 let s = x + y if s < 0 then return s else S.nil+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE filterAllIn #-} filterAllIn :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-filterAllIn t start = runStream . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+filterAllIn linearCount t start = runStream . t $ do x <- source start nestedCount2 y <- source start nestedCount2 let s = x + y if s > 0 then return s else S.nil+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE filterSome #-} filterSome :: (S.IsStream t, S.MonadAsync m, Monad (t m))- => (t m Int -> S.SerialT m Int) -> Int -> m ()-filterSome t start = runStream . t $ do+ => Int -> (t m Int -> S.SerialT m Int) -> Int -> m ()+filterSome linearCount t start = runStream . t $ do x <- source start nestedCount2 y <- source start nestedCount2 let s = x + y if s > 1100000 then return s else S.nil+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double)) {-# INLINE breakAfterSome #-} breakAfterSome :: (S.IsStream t, Monad (t IO))- => (t IO Int -> S.SerialT IO Int) -> Int -> IO ()-breakAfterSome t start = do+ => Int -> (t IO Int -> S.SerialT IO Int) -> Int -> IO ()+breakAfterSome linearCount t start = do (_ :: Either ErrorCall ()) <- try $ runStream . t $ do x <- source start nestedCount2 y <- source start nestedCount2@@ -162,3 +170,5 @@ then error "break" else return s return ()+ where+ nestedCount2 = round (fromIntegral linearCount**(1/2::Double))
benchmark/NestedUnfold.hs view
@@ -8,6 +8,8 @@ import Control.DeepSeq (NFData) import System.Random (randomRIO) +import Common (parseCLIOpts)+ import qualified NestedUnfoldOps as Ops import Gauge@@ -15,18 +17,22 @@ benchIO :: (NFData b) => String -> (Int -> IO b) -> Benchmark benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f +defaultStreamSize :: Int+defaultStreamSize = 100000+ main :: IO ()-main =- defaultMain+main = do+ (linearCount, cfg, benches) <- parseCLIOpts defaultStreamSize+ linearCount `seq` runMode (mode cfg) cfg benches [ bgroup "unfold"- [ benchIO "toNull" $ Ops.toNull- , benchIO "toNull3" $ Ops.toNull3- , benchIO "concat" $ Ops.concat- , benchIO "toList" $ Ops.toList- , benchIO "toListSome" $ Ops.toListSome- , benchIO "filterAllOut" $ Ops.filterAllOut- , benchIO "filterAllIn" $ Ops.filterAllIn- , benchIO "filterSome" $ Ops.filterSome- , benchIO "breakAfterSome" $ Ops.breakAfterSome+ [ benchIO "toNull" $ Ops.toNull linearCount+ , benchIO "toNull3" $ Ops.toNull3 linearCount+ , benchIO "concat" $ Ops.concat linearCount+ -- , benchIO "toList" $ Ops.toList+ , benchIO "toListSome" $ Ops.toListSome linearCount+ , benchIO "filterAllOut" $ Ops.filterAllOut linearCount+ , benchIO "filterAllIn" $ Ops.filterAllIn linearCount+ , benchIO "filterSome" $ Ops.filterSome linearCount+ , benchIO "breakAfterSome" $ Ops.breakAfterSome linearCount ] ]
benchmark/NestedUnfoldOps.hs view
@@ -13,20 +13,18 @@ import qualified Streamly.Internal.Data.Unfold as UF import qualified Streamly.Internal.Data.Fold as FL -linearCount :: Int-linearCount = 100000- -- n * (n + 1) / 2 == linearCount-concatCount :: Int-concatCount = 450+concatCount :: Int -> Int+concatCount linearCount =+ round (((1 + 8 * fromIntegral linearCount)**(1/2::Double) - 1) / 2) -- double nested loop-nestedCount2 :: Int-nestedCount2 = round (fromIntegral linearCount**(1/2::Double))+nestedCount2 :: Int -> Int+nestedCount2 linearCount = round (fromIntegral linearCount**(1/2::Double)) -- triple nested loop-nestedCount3 :: Int-nestedCount3 = round (fromIntegral linearCount**(1/3::Double))+nestedCount3 :: Int -> Int+nestedCount3 linearCount = round (fromIntegral linearCount**(1/3::Double)) ------------------------------------------------------------------------------- -- Stream generation and elimination@@ -42,18 +40,18 @@ ------------------------------------------------------------------------------- {-# INLINE toNull #-}-toNull :: MonadIO m => Int -> m ()-toNull start = do- let end = start + nestedCount2+toNull :: MonadIO m => Int -> Int -> m ()+toNull linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.map (\(x, y) -> x + y) $ UF.outerProduct (source end) (source end)) FL.drain (start, start) {-# INLINE toNull3 #-}-toNull3 :: MonadIO m => Int -> m ()-toNull3 start = do- let end = start + nestedCount3+toNull3 :: MonadIO m => Int -> Int -> m ()+toNull3 linearCount start = do+ let end = start + nestedCount3 linearCount UF.fold (UF.map (\(x, y) -> x + y) $ UF.outerProduct (source end)@@ -62,35 +60,35 @@ FL.drain (start, (start, start)) {-# INLINE concat #-}-concat :: MonadIO m => Int -> m ()-concat start = do- let end = start + concatCount+concat :: MonadIO m => Int -> Int -> m ()+concat linearCount start = do+ let end = start + concatCount linearCount UF.fold (UF.concat (source end) (source end)) FL.drain start {-# INLINE toList #-}-toList :: MonadIO m => Int -> m [Int]-toList start = do- let end = start + nestedCount2+toList :: MonadIO m => Int -> Int -> m [Int]+toList linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.map (\(x, y) -> x + y) $ UF.outerProduct (source end) (source end)) FL.toList (start, start) {-# INLINE toListSome #-}-toListSome :: MonadIO m => Int -> m [Int]-toListSome start = do- let end = start + nestedCount2+toListSome :: MonadIO m => Int -> Int -> m [Int]+toListSome linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.take 1000 $ (UF.map (\(x, y) -> x + y) $ UF.outerProduct (source end) (source end))) FL.toList (start, start) {-# INLINE filterAllOut #-}-filterAllOut :: MonadIO m => Int -> m ()-filterAllOut start = do- let end = start + nestedCount2+filterAllOut :: MonadIO m => Int -> Int -> m ()+filterAllOut linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.filter (< 0) $ UF.map (\(x, y) -> x + y)@@ -98,9 +96,9 @@ FL.drain (start, start) {-# INLINE filterAllIn #-}-filterAllIn :: MonadIO m => Int -> m ()-filterAllIn start = do- let end = start + nestedCount2+filterAllIn :: MonadIO m => Int -> Int -> m ()+filterAllIn linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.filter (> 0) $ UF.map (\(x, y) -> x + y)@@ -108,9 +106,9 @@ FL.drain (start, start) {-# INLINE filterSome #-}-filterSome :: MonadIO m => Int -> m ()-filterSome start = do- let end = start + nestedCount2+filterSome :: MonadIO m => Int -> Int -> m ()+filterSome linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.filter (> 1100000) $ UF.map (\(x, y) -> x + y)@@ -118,9 +116,9 @@ FL.drain (start, start) {-# INLINE breakAfterSome #-}-breakAfterSome :: MonadIO m => Int -> m ()-breakAfterSome start = do- let end = start + nestedCount2+breakAfterSome :: MonadIO m => Int -> Int -> m ()+breakAfterSome linearCount start = do+ let end = start + nestedCount2 linearCount UF.fold (UF.takeWhile (<= 1100000) $ UF.map (\(x, y) -> x + y)
+ benchmark/Parallel.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Main+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com++import Control.DeepSeq (NFData)+-- import Data.Functor.Identity (Identity, runIdentity)+import System.Random (randomRIO)++import Common (parseCLIOpts)++import Streamly+import Gauge++import qualified Streamly.Benchmark.Prelude as Ops+import qualified NestedOps as Nested++{-# INLINE benchIONested #-}+benchIONested :: (NFData b) => String -> (Int -> IO b) -> Benchmark+benchIONested name f = bench name $ nfIO $ randomRIO (1,1) >>= f++-- We need a monadic bind here to make sure that the function f does not get+-- completely optimized out by the compiler in some cases.+--+-- | Takes a fold method, and uses it with a default source.+{-# INLINE benchIO #-}+benchIO :: (IsStream t, NFData b) => Int -> String -> (t IO Int -> IO b) -> Benchmark+benchIO value name f = bench name $ nfIO $ randomRIO (1,1) >>= f . Ops.source value++-- | Takes a source, and uses it with a default drain/fold method.+{-# INLINE benchSrcIO #-}+benchSrcIO+ :: (t IO Int -> SerialT IO Int)+ -> String+ -> (Int -> t IO Int)+ -> Benchmark+benchSrcIO t name f+ = bench name $ nfIO $ randomRIO (1,1) >>= Ops.toNull t . f++defaultStreamSize :: Int+defaultStreamSize = 100000++linear :: Int -> Int -> [Benchmark]+linear value value2 =+ [ -- unfoldr is pure and works serially irrespective of the stream type+ benchSrcIO parallely "unfoldr" (Ops.sourceUnfoldr value)+ , benchSrcIO parallely "unfoldrM" (Ops.sourceUnfoldrM value)+ , benchSrcIO parallely "fromFoldable" (Ops.sourceFromFoldable value)+ , benchSrcIO parallely "fromFoldableM" (Ops.sourceFromFoldableM value)+ , benchSrcIO parallely "foldMapWith" (Ops.sourceFoldMapWith value)+ , benchSrcIO parallely "foldMapWithM" (Ops.sourceFoldMapWithM value)+ , benchSrcIO parallely "foldMapM" (Ops.sourceFoldMapM value)+ -- map/fmap are pure and therefore no concurrency would be added on top+ -- of what the source stream (i.e. unfoldrM) provides.+ , benchIO value "map" $ Ops.map' parallely 1+ , benchIO value "fmap" $ Ops.fmap' parallely 1+ , benchIO value "mapM" $ Ops.mapM parallely 1+ , benchIONested "concatMapWith (2,x/2)"+ (Ops.concatStreamsWith parallel 2 (value `div` 2))+ , benchIONested "concatMapWith (sqrt x,sqrt x)"+ (Ops.concatStreamsWith parallel value2 value2)+ , benchIONested "concatMapWith (sqrt x * 2,sqrt x / 2)"+ (Ops.concatStreamsWith parallel (value2 * 2) (value2 `div` 2))+ ]++nested :: Int -> [Benchmark]+nested value =+ [ benchIONested "toNullAp" $ Nested.toNullAp value parallely+ , benchIONested "toNull" $ Nested.toNull value parallely+ , benchIONested "toNull3" $ Nested.toNull3 value parallely+ -- , benchIO "toList" $ Ops.toList value parallely+ -- XXX fix thread blocked indefinitely in MVar+ -- , benchIO "toListSome" $ Ops.toListSome value parallely+ , benchIONested "filterAllOut" $ Nested.filterAllOut value parallely+ , benchIONested "filterAllIn" $ Nested.filterAllIn value parallely+ , benchIONested "filterSome" $ Nested.filterSome value parallely+ , benchIONested "breakAfterSome" $ Nested.breakAfterSome value parallely+ ]++main :: IO ()+main = do+ (value, cfg, benches) <- parseCLIOpts defaultStreamSize+ let value2 = round $ sqrt $ (fromIntegral value :: Double)+ value2 `seq` value `seq`+ runMode (mode cfg) cfg benches $+ [ bgroup "parallelly"+ [ bgroup "linear" $ linear value value2+ , bgroup "nested" $ nested value+ ]+ ]
benchmark/StreamDKOps.hs view
@@ -13,14 +13,13 @@ -- import Control.Monad (when) -- import Data.Maybe (isJust) import Prelude- (Monad, Int, (+), ($), (.), return, even, (>), (<=), div,- subtract, undefined, Maybe(..), not, (>>=),- maxBound, flip, (<$>), (<*>), round, (/), (**), (<))+ (Monad, Int, (+), (.), return, undefined, Maybe(..), round, (/),+ (**), (>)) import qualified Prelude as P -- import qualified Data.List as List -import qualified Streamly.Streams.StreamDK as S--- import qualified Streamly.Streams.Prelude as SP+import qualified Streamly.Internal.Data.Stream.StreamDK as S+-- import qualified Streamly.Internal.Data.Stream.Prelude as SP -- import qualified Streamly.Internal.Data.SVar as S value, value2, value3, value16, maxValue :: Int
benchmark/StreamDOps.hs view
@@ -18,7 +18,7 @@ maxBound, fmap, odd, (==), flip, (<$>), (<*>), round, (/), (**), (<)) import qualified Prelude as P -import qualified Streamly.Streams.StreamD as S+import qualified Streamly.Internal.Data.Stream.StreamD as S import qualified Streamly.Internal.Data.Unfold as UF -- We try to keep the total number of iterations same irrespective of nesting@@ -250,6 +250,10 @@ iterateTakeAll = iterateSource (S.take maxValue) maxIters iterateDropOne = iterateSource (S.drop 1) maxIters iterateDropWhileTrue = iterateSource (S.dropWhile (<= maxValue)) maxIters++{-# INLINE iterateM #-}+iterateM :: Monad m => Int -> Stream m Int+iterateM i = S.take maxIters (S.iterateM (\x -> return (x + 1)) (return i)) ------------------------------------------------------------------------------- -- Zipping and concat
benchmark/StreamKOps.hs view
@@ -19,8 +19,8 @@ import qualified Prelude as P import qualified Data.List as List -import qualified Streamly.Streams.StreamK as S-import qualified Streamly.Streams.Prelude as SP+import qualified Streamly.Internal.Data.Stream.StreamK as S+import qualified Streamly.Internal.Data.Stream.Prelude as SP import qualified Streamly.Internal.Data.SVar as S value, value2, value3, value16, maxValue :: Int
+ benchmark/Streamly/Benchmark/Data/Array.hs view
@@ -0,0 +1,96 @@+-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}++module Main (main) where++import Control.DeepSeq (NFData(..), deepseq)+import System.Random (randomRIO)++import qualified Streamly.Benchmark.Data.ArrayOps as Ops+import qualified Streamly.Internal.Data.Array as A+import qualified Streamly.Prelude as S++import Gauge++-------------------------------------------------------------------------------+--+-------------------------------------------------------------------------------++{-# INLINE benchIO #-}+benchIO :: NFData b => String -> (Int -> IO a) -> (a -> b) -> Benchmark+benchIO name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= return . f++-- Drain a source that generates an array in the IO monad+{-# INLINE benchIOSrc #-}+benchIOSrc :: (NFData a)+ => String -> (Int -> IO (Ops.Stream a)) -> Benchmark+benchIOSrc name src = benchIO name src id++{-# INLINE benchPureSink #-}+benchPureSink :: NFData b => String -> (Ops.Stream Int -> b) -> Benchmark+benchPureSink name f = benchIO name Ops.sourceIntFromTo f++{-# INLINE benchIO' #-}+benchIO' :: NFData b => String -> (Int -> IO a) -> (a -> IO b) -> Benchmark+benchIO' name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= f++{-# INLINE benchIOSink #-}+benchIOSink :: NFData b => String -> (Ops.Stream Int -> IO b) -> Benchmark+benchIOSink name f = benchIO' name Ops.sourceIntFromTo f++mkString :: String+mkString = "[1" ++ concat (replicate Ops.value ",1") ++ "]"++main :: IO ()+main =+ defaultMain+ [ bgroup "Data.Array"+ [ bgroup "generation"+ [ benchIOSrc "writeN . intFromTo" Ops.sourceIntFromTo+ , benchIOSrc "write . intFromTo" Ops.sourceIntFromToFromStream+ , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList+ , benchIOSrc "writeN . unfoldr" Ops.sourceUnfoldr+ , benchIOSrc "writeN . fromList" Ops.sourceFromList+ -- , benchPureSrc "writeN . IsList.fromList" Ops.sourceIsList+ -- , benchPureSrc "writeN . IsString.fromString" Ops.sourceIsString+ , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)+ , benchPureSink "show" Ops.showInstance+ ]+ , bgroup "elimination"+ [ benchPureSink "id" id+ , benchPureSink "==" Ops.eqInstance+ , benchPureSink "/=" Ops.eqInstanceNotEq+ , benchPureSink "<" Ops.ordInstance+ , benchPureSink "min" Ops.ordInstanceMin+ -- length is used to check for foldr/build fusion+ -- , benchPureSink "length . IsList.toList" (length . GHC.toList)+ , benchIOSink "foldl'" Ops.pureFoldl'+ , benchIOSink "read" (S.drain . S.unfold A.read)+ , benchIOSink "toStreamRev" (S.drain . A.toStreamRev)+#ifdef DEVBUILD+ , benchPureSink "foldable/foldl'" Ops.foldableFoldl'+ , benchPureSink "foldable/sum" Ops.foldableSum+#endif+ ]+ , bgroup "transformation"+ [ benchIOSink "scanl'" (Ops.scanl' 1)+ , benchIOSink "scanl1'" (Ops.scanl1' 1)+ , benchIOSink "map" (Ops.map 1)+ ]+ , bgroup "transformationX4"+ [ benchIOSink "scanl'" (Ops.scanl' 4)+ , benchIOSink "scanl1'" (Ops.scanl1' 4)+ , benchIOSink "map" (Ops.map 4)+ ]+ ]+ ]
+ benchmark/Streamly/Benchmark/Data/ArrayOps.hs view
@@ -0,0 +1,148 @@+-- |+-- Module : Streamly.Benchmark.Data.ArrayOps+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Streamly.Benchmark.Data.ArrayOps where++import Control.Monad.IO.Class (MonadIO)+import Prelude (Int, Bool, (+), ($), (==), (>), (.), Maybe(..), undefined)+import qualified Prelude as P+#ifdef DEVBUILD+import qualified Data.Foldable as F+#endif++import qualified Streamly as S hiding (foldMapWith, runStream)+import qualified Streamly.Internal.Data.Array as A+import qualified Streamly.Prelude as S++value :: Int+value = 100000++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Stream = A.Array++{-# INLINE sourceUnfoldr #-}+sourceUnfoldr :: MonadIO m => Int -> m (Stream Int)+sourceUnfoldr n = S.fold (A.writeN value) $ S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (cnt, cnt + 1))++{-# INLINE sourceIntFromTo #-}+sourceIntFromTo :: MonadIO m => Int -> m (Stream Int)+sourceIntFromTo n = S.fold (A.writeN value) $ S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromToFromStream #-}+sourceIntFromToFromStream :: MonadIO m => Int -> m (Stream Int)+sourceIntFromToFromStream n = S.fold A.write $ S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromToFromList #-}+sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)+sourceIntFromToFromList n = P.return $ A.fromList $ [n..n + value]++{-# INLINE sourceFromList #-}+sourceFromList :: MonadIO m => Int -> m (Stream Int)+sourceFromList n = S.fold (A.writeN value) $ S.fromList [n..n+value]+{-+{-# INLINE sourceIsList #-}+sourceIsList :: Int -> Stream Int+sourceIsList n = GHC.fromList [n..n+value]++{-# INLINE sourceIsString #-}+sourceIsString :: Int -> Stream P.Char+sourceIsString n = GHC.fromString (P.replicate (n + value) 'a')+-}+-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE composeN #-}+composeN :: P.Monad m+ => Int -> (Stream Int -> m (Stream Int)) -> Stream Int -> m (Stream Int)+composeN n f x =+ case n of+ 1 -> f x+ 2 -> f x P.>>= f+ 3 -> f x P.>>= f P.>>= f+ 4 -> f x P.>>= f P.>>= f P.>>= f+ _ -> undefined++{-# INLINE scanl' #-}+{-# INLINE scanl1' #-}+{-# INLINE map #-}++scanl', scanl1', map+ :: MonadIO m => Int -> Stream Int -> m (Stream Int)++{-# INLINE onArray #-}+onArray+ :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)+ -> Stream Int+ -> m (Stream Int)+onArray f arr = S.fold (A.writeN value) $ f $ (S.unfold A.read arr)++scanl' n = composeN n $ onArray $ S.scanl' (+) 0+scanl1' n = composeN n $ onArray $ S.scanl1' (+)+map n = composeN n $ onArray $ S.map (+1)++{-# INLINE eqInstance #-}+eqInstance :: Stream Int -> Bool+eqInstance src = src == src++{-# INLINE eqInstanceNotEq #-}+eqInstanceNotEq :: Stream Int -> Bool+eqInstanceNotEq src = src P./= src++{-# INLINE ordInstance #-}+ordInstance :: Stream Int -> Bool+ordInstance src = src P.< src++{-# INLINE ordInstanceMin #-}+ordInstanceMin :: Stream Int -> Stream Int+ordInstanceMin src = P.min src src++{-# INLINE showInstance #-}+showInstance :: Stream Int -> P.String+showInstance src = P.show src++{-# INLINE readInstance #-}+readInstance :: P.String -> Stream Int+readInstance str =+ let r = P.reads str+ in case r of+ [(x,"")] -> x+ _ -> P.error "readInstance: no parse"++{-# INLINE pureFoldl' #-}+pureFoldl' :: MonadIO m => Stream Int -> m Int+pureFoldl' = S.foldl' (+) 0 . S.unfold A.read++#ifdef DEVBUILD+{-# INLINE foldableFoldl' #-}+foldableFoldl' :: Stream Int -> Int+foldableFoldl' = F.foldl' (+) 0++{-# INLINE foldableSum #-}+foldableSum :: Stream Int -> Int+foldableSum = P.sum+#endif
+ benchmark/Streamly/Benchmark/Data/Prim/Array.hs view
@@ -0,0 +1,100 @@+-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}++module Main (main) where++import Control.DeepSeq (NFData(..))+import System.Random (randomRIO)++import qualified Streamly.Benchmark.Data.Prim.ArrayOps as Ops+import qualified Streamly.Internal.Data.Prim.Array as A+import qualified Streamly.Prelude as S++import Gauge++-------------------------------------------------------------------------------+--+-------------------------------------------------------------------------------++{-# INLINE benchIO #-}+benchIO :: NFData b => String -> (Int -> IO a) -> (a -> b) -> Benchmark+benchIO name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= return . f++-- Drain a source that generates an array in the IO monad+{-# INLINE benchIOSrc #-}+benchIOSrc :: A.Prim a => String -> (Int -> IO (Ops.Stream a)) -> Benchmark+benchIOSrc name src = benchIO name src id++{-# INLINE benchPureSink #-}+benchPureSink :: NFData b => String -> (Ops.Stream Int -> b) -> Benchmark+benchPureSink name f = benchIO name Ops.sourceIntFromTo f++{-# INLINE benchIO' #-}+benchIO' :: NFData b => String -> (Int -> IO a) -> (a -> IO b) -> Benchmark+benchIO' name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= f++{-# INLINE benchIOSink #-}+benchIOSink :: NFData b => String -> (Ops.Stream Int -> IO b) -> Benchmark+benchIOSink name f = benchIO' name Ops.sourceIntFromTo f++{-+mkString :: String+mkString = "[1" ++ concat (replicate Ops.value ",1") ++ "]"+-}++main :: IO ()+main =+ defaultMain+ [ bgroup "Data.Prim.Array"+ [ bgroup "generation"+ [ benchIOSrc "writeN . intFromTo" Ops.sourceIntFromTo+ , benchIOSrc "write . intFromTo" Ops.sourceIntFromToFromStream+ , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList+ , benchIOSrc "writeN . unfoldr" Ops.sourceUnfoldr+ , benchIOSrc "writeN . fromList" Ops.sourceFromList+ -- , benchPureSrc "writeN . IsList.fromList" Ops.sourceIsList+ -- , benchPureSrc "writeN . IsString.fromString" Ops.sourceIsString+ -- , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)+ , benchPureSink "show" Ops.showInstance+ ]+ , bgroup "elimination"+ [ benchPureSink "id" id+ , benchPureSink "==" Ops.eqInstance+ , benchPureSink "/=" Ops.eqInstanceNotEq+ , benchPureSink "<" Ops.ordInstance+ , benchPureSink "min" Ops.ordInstanceMin+ -- length is used to check for foldr/build fusion+ -- , benchPureSink "length . IsList.toList" (length . GHC.toList)+ , benchIOSink "foldl'" Ops.pureFoldl'+ , benchIOSink "read" (S.drain . S.unfold A.read)+ , benchIOSink "toStreamRev" (S.drain . A.toStreamRev)+#if 0+ -- PrimArray does not have a Foldable instance because it requires a+ -- Prim constraint. Though it should be possible to make an instance in+ -- the same way as we do in Memory.Array.+ , benchPureSink "foldable/foldl'" Ops.foldableFoldl'+ , benchPureSink "foldable/sum" Ops.foldableSum+#endif+ ]+ , bgroup "transformation"+ [ benchIOSink "scanl'" (Ops.scanl' 1)+ , benchIOSink "scanl1'" (Ops.scanl1' 1)+ , benchIOSink "map" (Ops.map 1)+ ]+ , bgroup "transformationX4"+ [ benchIOSink "scanl'" (Ops.scanl' 4)+ , benchIOSink "scanl1'" (Ops.scanl1' 4)+ , benchIOSink "map" (Ops.map 4)+ ]+ ]+ ]
+ benchmark/Streamly/Benchmark/Data/Prim/ArrayOps.hs view
@@ -0,0 +1,153 @@+-- |+-- Module : Streamly.Benchmark.Data.Prim.ArrayOps+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Streamly.Benchmark.Data.Prim.ArrayOps where++import Control.Monad.IO.Class (MonadIO)+import Prelude (Int, Bool, (+), ($), (==), (>), (.), Maybe(..), undefined)+import qualified Prelude as P+#ifdef DEVBUILD+-- import qualified Data.Foldable as F+#endif++import qualified Streamly as S hiding (foldMapWith, runStream)+import qualified Streamly.Internal.Data.Prim.Array as A+import qualified Streamly.Prelude as S++value :: Int+value = 100000++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Stream = A.PrimArray++{-# INLINE sourceUnfoldr #-}+sourceUnfoldr :: MonadIO m => Int -> m (Stream Int)+sourceUnfoldr n = S.fold (A.writeN value) $ S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (cnt, cnt + 1))++{-# INLINE sourceIntFromTo #-}+sourceIntFromTo :: MonadIO m => Int -> m (Stream Int)+sourceIntFromTo n = S.fold (A.writeN value) $ S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromToFromStream #-}+sourceIntFromToFromStream :: MonadIO m => Int -> m (Stream Int)+sourceIntFromToFromStream n = S.fold A.write $ S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromToFromList #-}+sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)+sourceIntFromToFromList n = P.return $ A.fromList $ [n..n + value]++{-# INLINE sourceFromList #-}+sourceFromList :: MonadIO m => Int -> m (Stream Int)+sourceFromList n = S.fold (A.writeN value) $ S.fromList [n..n+value]+{-+{-# INLINE sourceIsList #-}+sourceIsList :: Int -> Stream Int+sourceIsList n = GHC.fromList [n..n+value]++{-# INLINE sourceIsString #-}+sourceIsString :: Int -> Stream P.Char+sourceIsString n = GHC.fromString (P.replicate (n + value) 'a')+-}+-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE composeN #-}+composeN :: P.Monad m+ => Int -> (Stream Int -> m (Stream Int)) -> Stream Int -> m (Stream Int)+composeN n f x =+ case n of+ 1 -> f x+ 2 -> f x P.>>= f+ 3 -> f x P.>>= f P.>>= f+ 4 -> f x P.>>= f P.>>= f P.>>= f+ _ -> undefined++{-# INLINE scanl' #-}+{-# INLINE scanl1' #-}+{-# INLINE map #-}++scanl', scanl1', map+ :: MonadIO m => Int -> Stream Int -> m (Stream Int)++{-# INLINE onArray #-}+onArray+ :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)+ -> Stream Int+ -> m (Stream Int)+onArray f arr = S.fold (A.writeN value) $ f $ (S.unfold A.read arr)++scanl' n = composeN n $ onArray $ S.scanl' (+) 0+scanl1' n = composeN n $ onArray $ S.scanl1' (+)+map n = composeN n $ onArray $ S.map (+1)++{-# INLINE eqInstance #-}+eqInstance :: Stream Int -> Bool+eqInstance src = src == src++{-# INLINE eqInstanceNotEq #-}+eqInstanceNotEq :: Stream Int -> Bool+eqInstanceNotEq src = src P./= src++{-# INLINE ordInstance #-}+ordInstance :: Stream Int -> Bool+ordInstance src = src P.< src++{-# INLINE ordInstanceMin #-}+ordInstanceMin :: Stream Int -> Stream Int+ordInstanceMin src = P.min src src++{-# INLINE showInstance #-}+showInstance :: Stream Int -> P.String+showInstance src = P.show src++{-+{-# INLINE readInstance #-}+readInstance :: P.String -> Stream Int+readInstance str =+ let r = P.reads str+ in case r of+ [(x,"")] -> x+ _ -> P.error "readInstance: no parse"+-}++{-# INLINE pureFoldl' #-}+pureFoldl' :: MonadIO m => Stream Int -> m Int+pureFoldl' = S.foldl' (+) 0 . S.unfold A.read++#if 0+-- PrimArray does not have a Foldable instance because it reuqires a Prim+-- constraint. Though it should be possible to make an instance in the same way+-- as we do in Memory.Array.+{-# INLINE foldableFoldl' #-}+foldableFoldl' :: Stream Int -> Int+foldableFoldl' = F.foldl' (+) 0++{-# INLINE foldableSum #-}+foldableSum :: Stream Int -> Int+foldableSum = P.sum+#endif
+ benchmark/Streamly/Benchmark/Data/SmallArray.hs view
@@ -0,0 +1,95 @@+-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}++module Main (main) where++import Control.DeepSeq (NFData(..), deepseq)+import System.Random (randomRIO)++import qualified Streamly.Benchmark.Data.SmallArrayOps as Ops+import qualified Streamly.Internal.Data.SmallArray as A+import qualified Streamly.Prelude as S++import Gauge++-------------------------------------------------------------------------------+--+-------------------------------------------------------------------------------++{-# INLINE benchIO #-}+benchIO :: NFData b => String -> (Int -> IO a) -> (a -> b) -> Benchmark+benchIO name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= return . f++-- Drain a source that generates an array in the IO monad+{-# INLINE benchIOSrc #-}+benchIOSrc :: (NFData a)+ => String -> (Int -> IO (Ops.Stream a)) -> Benchmark+benchIOSrc name src = benchIO name src id++{-# INLINE benchPureSink #-}+benchPureSink :: NFData b => String -> (Ops.Stream Int -> b) -> Benchmark+benchPureSink name f = benchIO name Ops.sourceIntFromTo f++{-# INLINE benchIO' #-}+benchIO' :: NFData b => String -> (Int -> IO a) -> (a -> IO b) -> Benchmark+benchIO' name src f = bench name $ nfIO $+ randomRIO (1,1) >>= src >>= f++{-# INLINE benchIOSink #-}+benchIOSink :: NFData b => String -> (Ops.Stream Int -> IO b) -> Benchmark+benchIOSink name f = benchIO' name Ops.sourceIntFromTo f++mkString :: String+mkString =+ "fromListN " +++ show (Ops.value + 1) ++ " [1" ++ concat (replicate Ops.value ",1") ++ "]"++main :: IO ()+main =+ defaultMain+ [ bgroup "SmallArray"+ [ bgroup "generation"+ [ benchIOSrc "writeN . intFromTo" Ops.sourceIntFromTo+ , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList+ , benchIOSrc "writeN . unfoldr" Ops.sourceUnfoldr+ , benchIOSrc "writeN . fromList" Ops.sourceFromList+ , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)+ , benchPureSink "show" Ops.showInstance+ ]+ , bgroup "elimination"+ [ benchPureSink "id" id+ , benchPureSink "==" Ops.eqInstance+ , benchPureSink "/=" Ops.eqInstanceNotEq+ , benchPureSink "<" Ops.ordInstance+ , benchPureSink "min" Ops.ordInstanceMin+ -- length is used to check for foldr/build fusion+ -- , benchPureSink "length . IsList.toList" (length . GHC.toList)+ , benchIOSink "foldl'" Ops.pureFoldl'+ , benchIOSink "read" (S.drain . S.unfold A.read)+ , benchIOSink "toStreamRev" (S.drain . A.toStreamRev)+#ifdef DEVBUILD+ , benchPureSink "foldable/foldl'" Ops.foldableFoldl'+ , benchPureSink "foldable/sum" Ops.foldableSum+#endif+ ]+ , bgroup "transformation"+ [ benchIOSink "scanl'" (Ops.scanl' 1)+ , benchIOSink "scanl1'" (Ops.scanl1' 1)+ , benchIOSink "map" (Ops.map 1)+ ]+ , bgroup "transformationX4"+ [ benchIOSink "scanl'" (Ops.scanl' 4)+ , benchIOSink "scanl1'" (Ops.scanl1' 4)+ , benchIOSink "map" (Ops.map 4)+ ]+ ]+ ]
+ benchmark/Streamly/Benchmark/Data/SmallArrayOps.hs view
@@ -0,0 +1,136 @@+-- |+-- Module : Streamly.Benchmark.Data.SmallArrayOps+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Streamly.Benchmark.Data.SmallArrayOps where++import Control.Monad.IO.Class (MonadIO)+import Prelude (Int, Bool, (+), ($), (==), (>), (.), Maybe(..), undefined)+import qualified Prelude as P+#ifdef DEVBUILD+import qualified Data.Foldable as F+#endif++import qualified Streamly as S hiding (foldMapWith, runStream)+import qualified Streamly.Internal.Data.SmallArray as A+import qualified Streamly.Prelude as S++value :: Int+value = 128++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Stream = A.SmallArray++{-# INLINE sourceUnfoldr #-}+sourceUnfoldr :: MonadIO m => Int -> m (Stream Int)+sourceUnfoldr n = S.fold (A.writeN value) $ S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (cnt, cnt + 1))++{-# INLINE sourceIntFromTo #-}+sourceIntFromTo :: MonadIO m => Int -> m (Stream Int)+sourceIntFromTo n = S.fold (A.writeN value) $ S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromToFromList #-}+sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)+sourceIntFromToFromList n = P.return $ (A.fromListN value) $ [n..n + value]++{-# INLINE sourceFromList #-}+sourceFromList :: MonadIO m => Int -> m (Stream Int)+sourceFromList n = S.fold (A.writeN value) $ S.fromList [n..n+value]++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE composeN #-}+composeN :: P.Monad m+ => Int -> (Stream Int -> m (Stream Int)) -> Stream Int -> m (Stream Int)+composeN n f x =+ case n of+ 1 -> f x+ 2 -> f x P.>>= f+ 3 -> f x P.>>= f P.>>= f+ 4 -> f x P.>>= f P.>>= f P.>>= f+ _ -> undefined++{-# INLINE scanl' #-}+{-# INLINE scanl1' #-}+{-# INLINE map #-}++scanl', scanl1', map+ :: MonadIO m => Int -> Stream Int -> m (Stream Int)++{-# INLINE onArray #-}+onArray+ :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)+ -> Stream Int+ -> m (Stream Int)+onArray f arr = S.fold (A.writeN value) $ f $ (S.unfold A.read arr)++scanl' n = composeN n $ onArray $ S.scanl' (+) 0+scanl1' n = composeN n $ onArray $ S.scanl1' (+)+map n = composeN n $ onArray $ S.map (+1)++{-# INLINE eqInstance #-}+eqInstance :: Stream Int -> Bool+eqInstance src = src == src++{-# INLINE eqInstanceNotEq #-}+eqInstanceNotEq :: Stream Int -> Bool+eqInstanceNotEq src = src P./= src++{-# INLINE ordInstance #-}+ordInstance :: Stream Int -> Bool+ordInstance src = src P.< src++{-# INLINE ordInstanceMin #-}+ordInstanceMin :: Stream Int -> Stream Int+ordInstanceMin src = P.min src src++{-# INLINE showInstance #-}+showInstance :: Stream Int -> P.String+showInstance src = P.show src++{-# INLINE readInstance #-}+readInstance :: P.String -> Stream Int+readInstance str =+ let r = P.reads str+ in case r of+ [(x,"")] -> x+ _ -> P.error "readInstance: no parse"++{-# INLINE pureFoldl' #-}+pureFoldl' :: MonadIO m => Stream Int -> m Int+pureFoldl' = S.foldl' (+) 0 . S.unfold A.read++#ifdef DEVBUILD+{-# INLINE foldableFoldl' #-}+foldableFoldl' :: Stream Int -> Int+foldableFoldl' = F.foldl' (+) 0++{-# INLINE foldableSum #-}+foldableSum :: Stream Int -> Int+foldableSum = P.sum+#endif
+ benchmark/Streamly/Benchmark/FileIO/Array.hs view
@@ -0,0 +1,259 @@+-- |+-- Module : Streamly.Benchmark.FileIO.Array+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}++#ifdef __HADDOCK_VERSION__+#undef INSPECTION+#endif++#ifdef INSPECTION+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}+#endif++module Streamly.Benchmark.FileIO.Array+ (+ last+ , countBytes+ , countLines+ , countWords+ , sumBytes+ , cat+ , catOnException+ , catBracket+ , catBracketIO+ , catBracketStream+ , catBracketStreamIO+ , copy+ , linesUnlinesCopy+ , wordsUnwordsCopy+ , decodeUtf8Lenient+ , copyCodecUtf8Lenient+ )+where++import Data.Functor.Identity (runIdentity)+import Data.Word (Word8)+import System.IO (Handle, hClose)+import Prelude hiding (last)++import qualified Streamly.FileSystem.Handle as FH+import qualified Streamly.Memory.Array as A+import qualified Streamly.Prelude as S+import qualified Streamly.Data.Unicode.Stream as SS+import qualified Streamly.Internal.Data.Unicode.Stream as IUS++import qualified Streamly.Internal.FileSystem.Handle as IFH+import qualified Streamly.Internal.Memory.Array as IA+import qualified Streamly.Internal.Memory.ArrayStream as AS+import qualified Streamly.Internal.Data.Unfold as IUF+import qualified Streamly.Internal.Prelude as IP++#ifdef INSPECTION+import Foreign.Storable (Storable)+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))+import Test.Inspection+#endif++-- | Get the last byte from a file bytestream.+{-# INLINE last #-}+last :: Handle -> IO (Maybe Word8)+last inh = do+ let s = IFH.toChunks inh+ larr <- S.last s+ return $ case larr of+ Nothing -> Nothing+ Just arr -> IA.readIndex arr (A.length arr - 1)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'last+inspect $ 'last `hasNoType` ''Step+#endif++-- | Count the number of bytes in a file.+{-# INLINE countBytes #-}+countBytes :: Handle -> IO Int+countBytes inh =+ let s = IFH.toChunks inh+ in S.sum (S.map A.length s)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countBytes+inspect $ 'countBytes `hasNoType` ''Step+#endif++-- | Count the number of lines in a file.+{-# INLINE countLines #-}+countLines :: Handle -> IO Int+countLines = S.length . AS.splitOnSuffix 10 . IFH.toChunks++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countLines+inspect $ 'countLines `hasNoType` ''Step+#endif++-- XXX use a word splitting combinator instead of splitOn and test it.+-- | Count the number of lines in a file.+{-# INLINE countWords #-}+countWords :: Handle -> IO Int+countWords = S.length . AS.splitOn 32 . IFH.toChunks++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countWords+inspect $ 'countWords `hasNoType` ''Step+#endif++-- | Sum the bytes in a file.+{-# INLINE sumBytes #-}+sumBytes :: Handle -> IO Word8+sumBytes inh = do+ let foldlArr' f z = runIdentity . S.foldl' f z . IA.toStream+ let s = IFH.toChunks inh+ S.foldl' (\acc arr -> acc + foldlArr' (+) 0 arr) 0 s++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'sumBytes+inspect $ 'sumBytes `hasNoType` ''Step+#endif++-- | Send the file contents to /dev/null+{-# INLINE cat #-}+cat :: Handle -> Handle -> IO ()+cat devNull inh =+ S.fold (IFH.writeChunks devNull) $ IFH.toChunksWithBufferOf (256*1024) inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'cat+inspect $ 'cat `hasNoType` ''Step+#endif++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catBracket #-}+catBracket :: Handle -> Handle -> IO ()+catBracket devNull inh =+ let readEx = IUF.bracket return (\_ -> hClose inh)+ (IUF.supplyFirst FH.readChunksWithBufferOf (256*1024))+ in IUF.fold readEx (IFH.writeChunks devNull) inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catBracket+-- inspect $ 'catBracket `hasNoType` ''Step+#endif++{-# INLINE catBracketIO #-}+catBracketIO :: Handle -> Handle -> IO ()+catBracketIO devNull inh =+ let readEx = IUF.bracketIO return (\_ -> hClose inh)+ (IUF.supplyFirst FH.readChunksWithBufferOf (256*1024))+ in IUF.fold readEx (IFH.writeChunks devNull) inh++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catBracketStream #-}+catBracketStream :: Handle -> Handle -> IO ()+catBracketStream devNull inh =+ let readEx = S.bracket (return ()) (\_ -> hClose inh)+ (\_ -> IFH.toChunksWithBufferOf (256*1024) inh)+ in S.fold (IFH.writeChunks devNull) $ readEx++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catBracketStream+-- inspect $ 'catBracketStream `hasNoType` ''Step+#endif++{-# INLINE catBracketStreamIO #-}+catBracketStreamIO :: Handle -> Handle -> IO ()+catBracketStreamIO devNull inh =+ let readEx = IP.bracketIO (return ()) (\_ -> hClose inh)+ (\_ -> IFH.toChunksWithBufferOf (256*1024) inh)+ in S.fold (IFH.writeChunks devNull) $ readEx++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catOnException #-}+catOnException :: Handle -> Handle -> IO ()+catOnException devNull inh =+ let readEx = IUF.onException (\_ -> hClose inh)+ (IUF.supplyFirst FH.readChunksWithBufferOf (256*1024))+ in IUF.fold readEx (IFH.writeChunks devNull) inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catOnException+-- inspect $ 'catOnException `hasNoType` ''Step+#endif++-- | Copy file+{-# INLINE copy #-}+copy :: Handle -> Handle -> IO ()+copy inh outh =+ let s = IFH.toChunks inh+ in S.fold (IFH.writeChunks outh) s++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copy+inspect $ 'copy `hasNoType` ''Step+#endif++-- | Lines and unlines+{-# INLINE linesUnlinesCopy #-}+linesUnlinesCopy :: Handle -> Handle -> IO ()+linesUnlinesCopy inh outh =+ S.fold (IFH.writeWithBufferOf (1024*1024) outh)+ $ AS.interposeSuffix 10+ $ AS.splitOnSuffix 10+ $ IFH.toChunksWithBufferOf (1024*1024) inh++#ifdef INSPECTION+inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]+-- inspect $ 'linesUnlinesCopy `hasNoType` ''Step+#endif++-- | Words and unwords+{-# INLINE wordsUnwordsCopy #-}+wordsUnwordsCopy :: Handle -> Handle -> IO ()+wordsUnwordsCopy inh outh =+ S.fold (IFH.writeWithBufferOf (1024*1024) outh)+ $ AS.interpose 32+ -- XXX this is not correct word splitting combinator+ $ AS.splitOn 32+ $ IFH.toChunksWithBufferOf (1024*1024) inh++#ifdef INSPECTION+inspect $ hasNoTypeClassesExcept 'wordsUnwordsCopy [''Storable]+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step+#endif++{-# INLINE decodeUtf8Lenient #-}+decodeUtf8Lenient :: Handle -> IO ()+decodeUtf8Lenient inh =+ S.drain+ $ IUS.decodeUtf8ArraysLenient+ $ IFH.toChunksWithBufferOf (1024*1024) inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'decodeUtf8Lenient+-- inspect $ 'decodeUtf8Lenient `hasNoType` ''Step+-- inspect $ 'decodeUtf8Lenient `hasNoType` ''AT.FlattenState+-- inspect $ 'decodeUtf8Lenient `hasNoType` ''D.ConcatMapUState+#endif++-- | Copy file+{-# INLINE copyCodecUtf8Lenient #-}+copyCodecUtf8Lenient :: Handle -> Handle -> IO ()+copyCodecUtf8Lenient inh outh =+ S.fold (FH.write outh)+ $ SS.encodeUtf8+ $ IUS.decodeUtf8ArraysLenient+ $ IFH.toChunksWithBufferOf (1024*1024) inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState+#endif
+ benchmark/Streamly/Benchmark/FileIO/Stream.hs view
@@ -0,0 +1,651 @@+-- |+-- Module : Streamly.Benchmark.FileIO.Stream+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}++#ifdef __HADDOCK_VERSION__+#undef INSPECTION+#endif++#ifdef INSPECTION+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}+#endif++module Streamly.Benchmark.FileIO.Stream+ (+ -- * FileIO+ last+ , countBytes+ , countLines+ , countLinesU+ , countWords+ , sumBytes+ , cat+ , catStreamWrite+ , catBracket+ , catBracketIO+ , catBracketStream+ , catBracketStreamIO+ , catOnException+ , catOnExceptionStream+ , catHandle+ , catHandleStream+ , catFinally+ , catFinallyIO+ , catFinallyStream+ , catFinallyStreamIO+ , copy+ , linesUnlinesCopy+ , linesUnlinesArrayWord8Copy+ , linesUnlinesArrayCharCopy+ -- , linesUnlinesArrayUtf8Copy+ , wordsUnwordsCopyWord8+ , wordsUnwordsCopy+ , wordsUnwordsCharArrayCopy+ , readWord8+ , decodeLatin1+ , copyCodecChar8+ , copyCodecUtf8+ , decodeUtf8Lax+ , copyCodecUtf8Lenient+ , chunksOfSum+ , chunksOf+ , chunksOfD+ , splitOn+ , splitOnSuffix+ , wordsBy+ , splitOnSeq+ , splitOnSeqUtf8+ , splitOnSuffixSeq+ )+where++import Control.Exception (SomeException)+import Data.Char (ord, chr)+import Data.Word (Word8)+import System.IO (Handle, hClose)+import Prelude hiding (last, length)++import qualified Streamly.FileSystem.Handle as FH+import qualified Streamly.Internal.FileSystem.Handle as IFH+import qualified Streamly.Memory.Array as A+-- import qualified Streamly.Internal.Memory.Array as IA+import qualified Streamly.Internal.Memory.Array.Types as AT+import qualified Streamly.Prelude as S+import qualified Streamly.Data.Fold as FL+-- import qualified Streamly.Internal.Data.Fold as IFL+import qualified Streamly.Data.Unicode.Stream as SS+import qualified Streamly.Internal.Data.Unicode.Stream as IUS+import qualified Streamly.Internal.Memory.Unicode.Array as IUA+import qualified Streamly.Internal.Data.Unfold as IUF+import qualified Streamly.Internal.Prelude as IP+import qualified Streamly.Internal.Data.Stream.StreamD as D++#ifdef INSPECTION+import Foreign.Storable (Storable)+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..), GroupState)+import Test.Inspection+#endif++-- | Get the last byte from a file bytestream.+{-# INLINE last #-}+last :: Handle -> IO (Maybe Word8)+last = S.last . S.unfold FH.read++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'last+inspect $ 'last `hasNoType` ''Step+inspect $ 'last `hasNoType` ''AT.FlattenState+inspect $ 'last `hasNoType` ''D.ConcatMapUState+#endif++-- assert that flattenArrays constructors are not present+-- | Count the number of bytes in a file.+{-# INLINE countBytes #-}+countBytes :: Handle -> IO Int+countBytes = S.length . S.unfold FH.read++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countBytes+inspect $ 'countBytes `hasNoType` ''Step+inspect $ 'countBytes `hasNoType` ''AT.FlattenState+inspect $ 'countBytes `hasNoType` ''D.ConcatMapUState+#endif++-- | Count the number of lines in a file.+{-# INLINE countLines #-}+countLines :: Handle -> IO Int+countLines =+ S.length+ . IUS.lines FL.drain+ . SS.decodeLatin1+ . S.unfold FH.read++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countLines+inspect $ 'countLines `hasNoType` ''Step+inspect $ 'countLines `hasNoType` ''AT.FlattenState+inspect $ 'countLines `hasNoType` ''D.ConcatMapUState+#endif++-- | Count the number of words in a file.+{-# INLINE countWords #-}+countWords :: Handle -> IO Int+countWords =+ S.length+ . IUS.words FL.drain+ . SS.decodeLatin1+ . S.unfold FH.read++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countWords+-- inspect $ 'countWords `hasNoType` ''Step+-- inspect $ 'countWords `hasNoType` ''D.ConcatMapUState+#endif++-- | Count the number of lines in a file.+{-# INLINE countLinesU #-}+countLinesU :: Handle -> IO Int+countLinesU inh =+ S.length+ $ IUS.lines FL.drain+ $ SS.decodeLatin1+ $ S.concatUnfold A.read (IFH.toChunks inh)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'countLinesU+inspect $ 'countLinesU `hasNoType` ''Step+inspect $ 'countLinesU `hasNoType` ''D.ConcatMapUState+#endif++-- | Sum the bytes in a file.+{-# INLINE sumBytes #-}+sumBytes :: Handle -> IO Word8+sumBytes = S.sum . S.unfold FH.read++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'sumBytes+inspect $ 'sumBytes `hasNoType` ''Step+inspect $ 'sumBytes `hasNoType` ''AT.FlattenState+inspect $ 'sumBytes `hasNoType` ''D.ConcatMapUState+#endif++-- | Send the file contents to /dev/null+{-# INLINE cat #-}+cat :: Handle -> Handle -> IO ()+cat devNull inh = S.fold (FH.write devNull) $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'cat+inspect $ 'cat `hasNoType` ''Step+inspect $ 'cat `hasNoType` ''AT.FlattenState+inspect $ 'cat `hasNoType` ''D.ConcatMapUState+#endif++-- | Send the file contents to /dev/null+{-# INLINE catStreamWrite #-}+catStreamWrite :: Handle -> Handle -> IO ()+catStreamWrite devNull inh = IFH.fromBytes devNull $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catStreamWrite+inspect $ 'catStreamWrite `hasNoType` ''Step+inspect $ 'catStreamWrite `hasNoType` ''AT.FlattenState+inspect $ 'catStreamWrite `hasNoType` ''D.ConcatMapUState+#endif++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catBracket #-}+catBracket :: Handle -> Handle -> IO ()+catBracket devNull inh =+ let readEx = IUF.bracket return (\_ -> hClose inh) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catBracket+-- inspect $ 'catBracket `hasNoType` ''Step+-- inspect $ 'catBracket `hasNoType` ''AT.FlattenState+-- inspect $ 'catBracket `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE catBracketIO #-}+catBracketIO :: Handle -> Handle -> IO ()+catBracketIO devNull inh =+ let readEx = IUF.bracketIO return (\_ -> hClose inh) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catBracketStream #-}+catBracketStream :: Handle -> Handle -> IO ()+catBracketStream devNull inh =+ let readEx = S.bracket (return ()) (\_ -> hClose inh)+ (\_ -> IFH.toBytes inh)+ in IFH.fromBytes devNull $ readEx++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catBracketStream+-- inspect $ 'catBracketStream `hasNoType` ''Step+#endif++{-# INLINE catBracketStreamIO #-}+catBracketStreamIO :: Handle -> Handle -> IO ()+catBracketStreamIO devNull inh =+ let readEx = IP.bracketIO (return ()) (\_ -> hClose inh)+ (\_ -> IFH.toBytes inh)+ in IFH.fromBytes devNull $ readEx++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catOnException #-}+catOnException :: Handle -> Handle -> IO ()+catOnException devNull inh =+ let readEx = IUF.onException (\_ -> hClose inh) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catOnException+-- inspect $ 'catOnException `hasNoType` ''Step+-- inspect $ 'catOnException `hasNoType` ''AT.FlattenState+-- inspect $ 'catOnException `hasNoType` ''D.ConcatMapUState+#endif++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catOnExceptionStream #-}+catOnExceptionStream :: Handle -> Handle -> IO ()+catOnExceptionStream devNull inh =+ let readEx = S.onException (hClose inh) (S.unfold FH.read inh)+ in S.fold (FH.write devNull) $ readEx++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catFinally #-}+catFinally :: Handle -> Handle -> IO ()+catFinally devNull inh =+ let readEx = IUF.finally (\_ -> hClose inh) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catFinally+-- inspect $ 'catFinally `hasNoType` ''Step+-- inspect $ 'catFinally `hasNoType` ''AT.FlattenState+-- inspect $ 'catFinally `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE catFinallyIO #-}+catFinallyIO :: Handle -> Handle -> IO ()+catFinallyIO devNull inh =+ let readEx = IUF.finallyIO (\_ -> hClose inh) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catFinallyStream #-}+catFinallyStream :: Handle -> Handle -> IO ()+catFinallyStream devNull inh =+ let readEx = S.finally (hClose inh) (S.unfold FH.read inh)+ in S.fold (FH.write devNull) readEx++{-# INLINE catFinallyStreamIO #-}+catFinallyStreamIO :: Handle -> Handle -> IO ()+catFinallyStreamIO devNull inh =+ let readEx = IP.finallyIO (hClose inh) (S.unfold FH.read inh)+ in S.fold (FH.write devNull) readEx++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catHandle #-}+catHandle :: Handle -> Handle -> IO ()+catHandle devNull inh =+ let handler (_e :: SomeException) = hClose inh >> return 10+ readEx = IUF.handle (IUF.singleton handler) FH.read+ in S.fold (FH.write devNull) $ S.unfold readEx inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'catHandle+-- inspect $ 'catHandle `hasNoType` ''Step+-- inspect $ 'catHandle `hasNoType` ''AT.FlattenState+-- inspect $ 'catHandle `hasNoType` ''D.ConcatMapUState+#endif++-- | Send the file contents to /dev/null with exception handling+{-# INLINE catHandleStream #-}+catHandleStream :: Handle -> Handle -> IO ()+catHandleStream devNull inh =+ let handler (_e :: SomeException) = S.yieldM (hClose inh >> return 10)+ readEx = S.handle handler (S.unfold FH.read inh)+ in S.fold (FH.write devNull) $ readEx++-- | Copy file+{-# INLINE copy #-}+copy :: Handle -> Handle -> IO ()+copy inh outh = S.fold (FH.write outh) (S.unfold FH.read inh)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copy+inspect $ 'copy `hasNoType` ''Step+inspect $ 'copy `hasNoType` ''AT.FlattenState+inspect $ 'copy `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE readWord8 #-}+readWord8 :: Handle -> IO ()+readWord8 inh = S.drain $ S.unfold FH.read inh++{-# INLINE decodeLatin1 #-}+decodeLatin1 :: Handle -> IO ()+decodeLatin1 inh =+ S.drain+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++-- | Copy file+{-# INLINE copyCodecChar8 #-}+copyCodecChar8 :: Handle -> Handle -> IO ()+copyCodecChar8 inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copyCodecChar8+inspect $ 'copyCodecChar8 `hasNoType` ''Step+inspect $ 'copyCodecChar8 `hasNoType` ''AT.FlattenState+inspect $ 'copyCodecChar8 `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE decodeUtf8Lax #-}+decodeUtf8Lax :: Handle -> IO ()+decodeUtf8Lax inh =+ S.drain+ $ SS.decodeUtf8Lax+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'decodeUtf8Lax+-- inspect $ 'decodeUtf8Lax `hasNoType` ''Step+-- inspect $ 'decodeUtf8Lax `hasNoType` ''AT.FlattenState+-- inspect $ 'decodeUtf8Lax `hasNoType` ''D.ConcatMapUState+#endif++-- | Copy file+{-# INLINE copyCodecUtf8 #-}+copyCodecUtf8 :: Handle -> Handle -> IO ()+copyCodecUtf8 inh outh =+ S.fold (FH.write outh)+ $ SS.encodeUtf8+ $ SS.decodeUtf8+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copyCodecUtf8+-- inspect $ 'copyCodecUtf8 `hasNoType` ''Step+-- inspect $ 'copyCodecUtf8 `hasNoType` ''AT.FlattenState+-- inspect $ 'copyCodecUtf8 `hasNoType` ''D.ConcatMapUState+#endif++-- | Copy file+{-# INLINE copyCodecUtf8Lenient #-}+copyCodecUtf8Lenient :: Handle -> Handle -> IO ()+copyCodecUtf8Lenient inh outh =+ S.fold (FH.write outh)+ $ SS.encodeUtf8+ $ SS.decodeUtf8Lax+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState+-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE chunksOfSum #-}+chunksOfSum :: Int -> Handle -> IO Int+chunksOfSum n inh = S.length $ S.chunksOf n FL.sum (S.unfold FH.read inh)++-- | Slice in chunks of size n and get the count of chunks.+{-# INLINE chunksOf #-}+chunksOf :: Int -> Handle -> IO Int+chunksOf n inh =+ -- writeNUnsafe gives 2.5x boost here over writeN.+ S.length $ S.chunksOf n (AT.writeNUnsafe n) (S.unfold FH.read inh)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'chunksOf+inspect $ 'chunksOf `hasNoType` ''Step+inspect $ 'chunksOf `hasNoType` ''AT.FlattenState+inspect $ 'chunksOf `hasNoType` ''D.ConcatMapUState+inspect $ 'chunksOf `hasNoType` ''GroupState+#endif++-- This is to make sure that the concatMap in FH.read, groupsOf and foldlM'+-- together can fuse.+--+-- | Slice in chunks of size n and get the count of chunks.+{-# INLINE chunksOfD #-}+chunksOfD :: Int -> Handle -> IO Int+chunksOfD n inh =+ D.foldlM' (\i _ -> return $ i + 1) 0+ $ D.groupsOf n (AT.writeNUnsafe n)+ $ D.fromStreamK (S.unfold FH.read inh)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'chunksOf+inspect $ 'chunksOf `hasNoType` ''Step+inspect $ 'chunksOfD `hasNoType` ''GroupState+inspect $ 'chunksOfD `hasNoType` ''AT.FlattenState+inspect $ 'chunksOfD `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE linesUnlinesCopy #-}+linesUnlinesCopy :: Handle -> Handle -> IO ()+linesUnlinesCopy inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ IUS.unlines IUF.fromList+ $ S.splitOnSuffix (== '\n') FL.toList+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++{-# INLINE linesUnlinesArrayWord8Copy #-}+linesUnlinesArrayWord8Copy :: Handle -> Handle -> IO ()+linesUnlinesArrayWord8Copy inh outh =+ S.fold (FH.write outh)+ $ IP.interposeSuffix 10 A.read+ $ S.splitOnSuffix (== 10) A.write+ $ S.unfold FH.read inh++-- XXX splitSuffixOn requires -funfolding-use-threshold=150 for better fusion+-- | Lines and unlines+{-# INLINE linesUnlinesArrayCharCopy #-}+linesUnlinesArrayCharCopy :: Handle -> Handle -> IO ()+linesUnlinesArrayCharCopy inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ IUA.unlines+ $ IUA.lines+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClassesExcept 'linesUnlinesArrayCharCopy [''Storable]+-- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''Step+-- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''AT.FlattenState+-- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''D.ConcatMapUState+#endif++-- XXX to write this we need to be able to map decodeUtf8 on the A.read fold.+-- For that we have to write decodeUtf8 as a Pipe.+{-+{-# INLINE linesUnlinesArrayUtf8Copy #-}+linesUnlinesArrayUtf8Copy :: Handle -> Handle -> IO ()+linesUnlinesArrayUtf8Copy inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ IP.intercalate (A.fromList [10]) (pipe SS.decodeUtf8P A.read)+ $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8 A.write)+ $ SS.decodeLatin1+ $ S.unfold FH.read inh+-}++foreign import ccall unsafe "u_iswspace"+ iswspace :: Int -> Int++-- Code copied from base/Data.Char to INLINE it+{-# INLINE isSpace #-}+isSpace :: Char -> Bool+-- isSpace includes non-breaking space+-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints+-- at or below 0x377 have been assigned, so we shouldn't have to worry about+-- any new spaces appearing below there. It would probably be best to+-- use branchless ||, but currently the eqLit transformation will undo that,+-- so we'll do it like this until there's a way around that.+isSpace c+ | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0+ | otherwise = iswspace (ord c) /= 0+ where+ uc = fromIntegral (ord c) :: Word++{-# INLINE isSp #-}+isSp :: Word8 -> Bool+isSp = isSpace . chr . fromIntegral++-- | Word, unwords and copy+{-# INLINE wordsUnwordsCopyWord8 #-}+wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()+wordsUnwordsCopyWord8 inh outh =+ S.fold (FH.write outh)+ $ IP.interposeSuffix 32 IUF.fromList+ $ S.wordsBy isSp FL.toList+ $ S.unfold FH.read inh++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8+-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step+-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''D.ConcatMapUState+#endif++-- | Word, unwords and copy+{-# INLINE wordsUnwordsCopy #-}+wordsUnwordsCopy :: Handle -> Handle -> IO ()+wordsUnwordsCopy inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ IUS.unwords IUF.fromList+ -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn+ -- with -funfolding-use-threshold=300. With wordsBy it does not fuse+ -- even with high limits for inlining and spec-constr ghc options. With+ -- -funfolding-use-threshold=400 it performs pretty well and there+ -- is no evidence in the core that a join point involving Step+ -- constructors is not getting inlined. Not being able to fuse at all in+ -- this case could be an unknown issue, need more investigation.+ $ S.wordsBy isSpace FL.toList+ -- -- $ S.splitOn isSpace FL.toList+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++#ifdef INSPECTION+-- inspect $ hasNoTypeClasses 'wordsUnwordsCopy+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''AT.FlattenState+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE wordsUnwordsCharArrayCopy #-}+wordsUnwordsCharArrayCopy :: Handle -> Handle -> IO ()+wordsUnwordsCharArrayCopy inh outh =+ S.fold (FH.write outh)+ $ SS.encodeLatin1+ $ IUA.unwords+ $ IUA.words+ $ SS.decodeLatin1+ $ S.unfold FH.read inh++lf :: Word8+lf = fromIntegral (ord '\n')++toarr :: String -> A.Array Word8+toarr = A.fromList . map (fromIntegral . ord)++-- | Split on line feed.+{-# INLINE splitOn #-}+splitOn :: Handle -> IO Int+splitOn inh =+ (S.length $ S.splitOn (== lf) FL.drain+ $ S.unfold FH.read inh) -- >>= print++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'splitOn+inspect $ 'splitOn `hasNoType` ''Step+inspect $ 'splitOn `hasNoType` ''AT.FlattenState+inspect $ 'splitOn `hasNoType` ''D.ConcatMapUState+#endif++-- | Split suffix on line feed.+{-# INLINE splitOnSuffix #-}+splitOnSuffix :: Handle -> IO Int+splitOnSuffix inh =+ (S.length $ S.splitOnSuffix (== lf) FL.drain+ $ S.unfold FH.read inh) -- >>= print++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'splitOnSuffix+inspect $ 'splitOnSuffix `hasNoType` ''Step+inspect $ 'splitOnSuffix `hasNoType` ''AT.FlattenState+inspect $ 'splitOnSuffix `hasNoType` ''D.ConcatMapUState+#endif++-- | Words by space+{-# INLINE wordsBy #-}+wordsBy :: Handle -> IO Int+wordsBy inh =+ (S.length $ S.wordsBy isSp FL.drain+ $ S.unfold FH.read inh) -- >>= print++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'wordsBy+inspect $ 'wordsBy `hasNoType` ''Step+inspect $ 'wordsBy `hasNoType` ''AT.FlattenState+inspect $ 'wordsBy `hasNoType` ''D.ConcatMapUState+#endif++-- | Split on a word8 sequence.+{-# INLINE splitOnSeq #-}+splitOnSeq :: String -> Handle -> IO Int+splitOnSeq str inh =+ (S.length $ IP.splitOnSeq (toarr str) FL.drain+ $ S.unfold FH.read inh) -- >>= print++#ifdef INSPECTION+-- inspect $ hasNoTypeClasses 'splitOnSeq+-- inspect $ 'splitOnSeq `hasNoType` ''Step+-- inspect $ 'splitOnSeq `hasNoType` ''AT.FlattenState+-- inspect $ 'splitOnSeq `hasNoType` ''D.ConcatMapUState+#endif++-- | Split on a character sequence.+{-# INLINE splitOnSeqUtf8 #-}+splitOnSeqUtf8 :: String -> Handle -> IO Int+splitOnSeqUtf8 str inh =+ (S.length $ IP.splitOnSeq (A.fromList str) FL.drain+ $ IUS.decodeUtf8ArraysLenient+ $ IFH.toChunks inh) -- >>= print++-- | Split on suffix sequence.+{-# INLINE splitOnSuffixSeq #-}+splitOnSuffixSeq :: String -> Handle -> IO Int+splitOnSuffixSeq str inh =+ (S.length $ IP.splitOnSuffixSeq (toarr str) FL.drain+ $ S.unfold FH.read inh) -- >>= print++#ifdef INSPECTION+-- inspect $ hasNoTypeClasses 'splitOnSuffixSeq+-- inspect $ 'splitOnSuffixSeq `hasNoType` ''Step+-- inspect $ 'splitOnSuffixSeq `hasNoType` ''AT.FlattenState+-- inspect $ 'splitOnSuffixSeq `hasNoType` ''D.ConcatMapUState+#endif
+ benchmark/Streamly/Benchmark/Prelude.hs view
@@ -0,0 +1,1202 @@+-- |+-- Module : Streamly.Benchmark.Prelude+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : streamly@composewell.com++{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RankNTypes #-}++#ifdef __HADDOCK_VERSION__+#undef INSPECTION+#endif++#ifdef INSPECTION+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}+#endif++module Streamly.Benchmark.Prelude where++import Control.DeepSeq (NFData)+import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.State.Strict (StateT, get, put)+import Data.Functor.Identity (Identity, runIdentity)+import Data.IORef (newIORef, modifyIORef')+import GHC.Generics (Generic)+import Prelude+ (Monad, Int, (+), ($), (.), return, fmap, even, (>), (<=), (==), (>=),+ subtract, undefined, Maybe(..), odd, Bool, not, (>>=), mapM_, curry,+ maxBound, div, IO, compare, Double, fromIntegral, Integer, (<$>),+ (<*>), flip)+import qualified Prelude as P+import qualified Data.Foldable as F+import qualified GHC.Exts as GHC++#ifdef INSPECTION+import Test.Inspection++import qualified Streamly.Internal.Data.Stream.StreamD as D+#endif++import qualified Streamly as S hiding (runStream)+import qualified Streamly.Prelude as S+import qualified Streamly.Internal.Prelude as Internal+import qualified Streamly.Internal.Data.Fold as FL+import qualified Streamly.Internal.Data.Unfold as UF+import qualified Streamly.Internal.Data.Pipe as Pipe+import qualified Streamly.Internal.Data.Stream.Parallel as Par+import Streamly.Internal.Data.Time.Units++type Stream m a = S.SerialT m a++-------------------------------------------------------------------------------+-- Stream generation+-------------------------------------------------------------------------------++-- enumerate++{-# INLINE sourceIntFromTo #-}+sourceIntFromTo :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceIntFromTo value n = S.enumerateFromTo n (n + value)++{-# INLINE sourceIntFromThenTo #-}+sourceIntFromThenTo :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceIntFromThenTo value n = S.enumerateFromThenTo n (n + 1) (n + value)++{-# INLINE sourceFracFromTo #-}+sourceFracFromTo :: (Monad m, S.IsStream t) => Int -> Int -> t m Double+sourceFracFromTo value n =+ S.enumerateFromTo (fromIntegral n) (fromIntegral (n + value))++{-# INLINE sourceFracFromThenTo #-}+sourceFracFromThenTo :: (Monad m, S.IsStream t) => Int -> Int -> t m Double+sourceFracFromThenTo value n = S.enumerateFromThenTo (fromIntegral n)+ (fromIntegral n + 1.0001) (fromIntegral (n + value))++{-# INLINE sourceIntegerFromStep #-}+sourceIntegerFromStep :: (Monad m, S.IsStream t) => Int -> Int -> t m Integer+sourceIntegerFromStep value n =+ S.take value $ S.enumerateFromThen (fromIntegral n) (fromIntegral n + 1)++-- unfoldr++{-# INLINE sourceUnfoldr #-}+sourceUnfoldr :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceUnfoldr value n = S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else Just (cnt, cnt + 1)++{-# INLINE sourceUnfoldrN #-}+sourceUnfoldrN :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceUnfoldrN upto start = S.unfoldr step start+ where+ step cnt =+ if cnt > start + upto+ then Nothing+ else Just (cnt, cnt + 1)++{-# INLINE sourceUnfoldrM #-}+sourceUnfoldrM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int+sourceUnfoldrM value n = S.unfoldrM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (cnt, cnt + 1))++{-# INLINE source #-}+source :: (S.MonadAsync m, S.IsStream t) => Int -> Int -> t m Int+source = sourceUnfoldrM++{-# INLINE sourceUnfoldrMN #-}+sourceUnfoldrMN :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int+sourceUnfoldrMN upto start = S.unfoldrM step start+ where+ step cnt =+ if cnt > start + upto+ then return Nothing+ else return (Just (cnt, cnt + 1))++{-# INLINE sourceUnfoldrMAction #-}+sourceUnfoldrMAction :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m (m Int)+sourceUnfoldrMAction value n = S.serially $ S.unfoldrM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (return cnt, cnt + 1))++{-# INLINE sourceUnfoldrAction #-}+sourceUnfoldrAction :: (S.IsStream t, Monad m, Monad m1)+ => Int -> Int -> t m (m1 Int)+sourceUnfoldrAction value n = S.serially $ S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (return cnt, cnt + 1))++-- fromIndices++{-# INLINE sourceFromIndices #-}+sourceFromIndices :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceFromIndices value n = S.take value $ S.fromIndices (+ n)++{-# INLINE sourceFromIndicesM #-}+sourceFromIndicesM :: (S.MonadAsync m, S.IsStream t) => Int -> Int -> t m Int+sourceFromIndicesM value n = S.take value $ S.fromIndicesM (Prelude.fmap return (+ n))++-- fromList++{-# INLINE sourceFromList #-}+sourceFromList :: (Monad m, S.IsStream t) => Int -> Int -> t m Int+sourceFromList value n = S.fromList [n..n+value]++{-# INLINE sourceFromListM #-}+sourceFromListM :: (S.MonadAsync m, S.IsStream t) => Int -> Int -> t m Int+sourceFromListM value n = S.fromListM (Prelude.fmap return [n..n+value])++{-# INLINE sourceIsList #-}+sourceIsList :: Int -> Int -> S.SerialT Identity Int+sourceIsList value n = GHC.fromList [n..n+value]++{-# INLINE sourceIsString #-}+sourceIsString :: Int -> Int -> S.SerialT Identity P.Char+sourceIsString value n = GHC.fromString (P.replicate (n + value) 'a')++-- fromFoldable++{-# INLINE sourceFromFoldable #-}+sourceFromFoldable :: S.IsStream t => Int -> Int -> t m Int+sourceFromFoldable value n = S.fromFoldable [n..n+value]++{-# INLINE sourceFromFoldableM #-}+sourceFromFoldableM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int+sourceFromFoldableM value n = S.fromFoldableM (Prelude.fmap return [n..n+value])++{-# INLINE currentTime #-}+currentTime :: (S.IsStream t, S.MonadAsync m)+ => Int -> Double -> Int -> t m AbsTime+currentTime value g _ = S.take value $ Internal.currentTime g++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++{-# INLINE runStream #-}+runStream :: Monad m => Stream m a -> m ()+runStream = S.drain++{-# INLINE toList #-}+toList :: Monad m => Stream m Int -> m [Int]++{-# INLINE head #-}+{-# INLINE last #-}+{-# INLINE maximum #-}+{-# INLINE minimum #-}+{-# INLINE find #-}+{-# INLINE findIndex #-}+{-# INLINE elemIndex #-}+{-# INLINE foldl1'Reduce #-}+head, last, minimum, maximum, foldl1'Reduce+ :: Monad m => Stream m Int -> m (Maybe Int)++find, findIndex, elemIndex+ :: Monad m => Int -> Stream m Int -> m (Maybe Int)++{-# INLINE minimumBy #-}+{-# INLINE maximumBy #-}+minimumBy, maximumBy :: Monad m => Stream m Int -> m (Maybe Int)++{-# INLINE foldl'Reduce #-}+{-# INLINE foldl'ReduceMap #-}+{-# INLINE foldlM'Reduce #-}+{-# INLINE foldrMReduce #-}+{-# INLINE length #-}+{-# INLINE sum #-}+{-# INLINE product #-}+foldl'Reduce, foldl'ReduceMap, foldlM'Reduce, foldrMReduce, length, sum, product+ :: Monad m+ => Stream m Int -> m Int++{-# INLINE foldl'Build #-}+{-# INLINE foldlM'Build #-}+{-# INLINE foldrMBuild #-}+foldrMBuild, foldl'Build, foldlM'Build+ :: Monad m+ => Stream m Int -> m [Int]++{-# INLINE all #-}+{-# INLINE any #-}+{-# INLINE and #-}+{-# INLINE or #-}+{-# INLINE null #-}+{-# INLINE elem #-}+{-# INLINE notElem #-}+null :: Monad m => Stream m Int -> m Bool++elem, notElem, all, any, and, or :: Monad m => Int -> Stream m Int -> m Bool++{-# INLINE toNull #-}+toNull :: Monad m => (t m a -> S.SerialT m a) -> t m a -> m ()+toNull t = runStream . t++{-# INLINE uncons #-}+uncons :: Monad m => Stream m Int -> m ()+uncons s = do+ r <- S.uncons s+ case r of+ Nothing -> return ()+ Just (_, t) -> uncons t++{-# INLINE init #-}+init :: Monad m => Stream m a -> m ()+init s = S.init s >>= Prelude.mapM_ S.drain++{-# INLINE tail #-}+tail :: Monad m => Stream m a -> m ()+tail s = S.tail s >>= Prelude.mapM_ tail++{-# INLINE nullHeadTail #-}+nullHeadTail :: Monad m => Stream m Int -> m ()+nullHeadTail s = do+ r <- S.null s+ when (not r) $ do+ _ <- S.head s+ S.tail s >>= Prelude.mapM_ nullHeadTail++{-# INLINE mapM_ #-}+mapM_ :: Monad m => Stream m Int -> m ()+mapM_ = S.mapM_ (\_ -> return ())++toList = S.toList++{-# INLINE toListRev #-}+toListRev :: Monad m => Stream m Int -> m [Int]+toListRev = Internal.toListRev++{-# INLINE foldrMElem #-}+foldrMElem :: Monad m => Int -> Stream m Int -> m Bool+foldrMElem e m = S.foldrM (\x xs -> if x == e then return P.True else xs)+ (return P.False) m++{-# INLINE foldrMToStream #-}+foldrMToStream :: Monad m => Stream m Int -> m (Stream Identity Int)+foldrMToStream = S.foldr S.cons S.nil++foldrMBuild = S.foldrM (\x xs -> xs >>= return . (x :)) (return [])+foldl'Build = S.foldl' (flip (:)) []+foldlM'Build = S.foldlM' (\xs x -> return $ x : xs) []++foldrMReduce = S.foldrM (\x xs -> xs >>= return . (x +)) (return 0)+foldl'Reduce = S.foldl' (+) 0+foldl'ReduceMap = P.fmap (+1) . S.foldl' (+) 0+foldl1'Reduce = S.foldl1' (+)+foldlM'Reduce = S.foldlM' (\xs a -> return $ a + xs) 0++last = S.last+null = S.null+head = S.head+elem value = S.elem (value + 1)+notElem value = S.notElem (value + 1)+length = S.length+all value = S.all (<= (value + 1))+any value = S.any (> (value + 1))+and value = S.and . S.map (<= (value + 1))+or value = S.or . S.map (> (value + 1))+find value = S.find (== (value + 1))+findIndex value = S.findIndex (== (value + 1))+elemIndex value = S.elemIndex (value + 1)+maximum = S.maximum+minimum = S.minimum+sum = S.sum+product = S.product+minimumBy = S.minimumBy compare+maximumBy = S.maximumBy compare++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Stream m a -> m ()+transform = runStream++{-# INLINE composeN #-}+composeN+ :: MonadIO m+ => Int -> (Stream m Int -> Stream m Int) -> Stream m Int -> m ()+composeN n f =+ case n of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined++-- polymorphic stream version of composeN+{-# INLINE composeN' #-}+composeN'+ :: (S.IsStream t, Monad m)+ => Int -> (t m Int -> Stream m Int) -> t m Int -> m ()+composeN' n f =+ case n of+ 1 -> transform . f+ 2 -> transform . f . S.adapt . f+ 3 -> transform . f . S.adapt . f . S.adapt . f+ 4 -> transform . f . S.adapt . f . S.adapt . f . S.adapt . f+ _ -> undefined++{-# INLINE scan #-}+{-# INLINE scanl1' #-}+{-# INLINE map #-}+{-# INLINE fmap #-}+{-# INLINE mapMaybe #-}+{-# INLINE filterEven #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE takeWhileMTrue #-}+{-# INLINE dropOne #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE dropWhileMTrue #-}+{-# INLINE dropWhileFalse #-}+{-# INLINE findIndices #-}+{-# INLINE elemIndices #-}+{-# INLINE insertBy #-}+{-# INLINE deleteBy #-}+{-# INLINE reverse #-}+{-# INLINE reverse' #-}+{-# INLINE foldrS #-}+{-# INLINE foldrSMap #-}+{-# INLINE foldrT #-}+{-# INLINE foldrTMap #-}+scan, scanl1', map, fmap, mapMaybe, filterEven,+ takeOne, dropOne,+ reverse, reverse',+ foldrS, foldrSMap, foldrT, foldrTMap+ :: MonadIO m+ => Int -> Stream m Int -> m ()++filterAllOut,+ filterAllIn, takeAll, takeWhileTrue, takeWhileMTrue,+ dropAll, dropWhileTrue, dropWhileMTrue, dropWhileFalse,+ findIndices, elemIndices, insertBy, deleteBy+ :: MonadIO m+ => Int -> Int -> Stream m Int -> m ()++{-# INLINE mapMaybeM #-}+{-# INLINE intersperse #-}+mapMaybeM :: S.MonadAsync m => Int -> Stream m Int -> m ()+intersperse :: S.MonadAsync m => Int -> Int -> Stream m Int -> m ()++{-# INLINE mapM #-}+{-# INLINE map' #-}+{-# INLINE fmap' #-}+mapM, map' :: (S.IsStream t, S.MonadAsync m)+ => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()++fmap' :: (S.IsStream t, S.MonadAsync m, P.Functor (t m))+ => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()++{-# INLINE sequence #-}+sequence :: (S.IsStream t, S.MonadAsync m)+ => (t m Int -> S.SerialT m Int) -> t m (m Int) -> m ()++scan n = composeN n $ S.scanl' (+) 0+scanl1' n = composeN n $ S.scanl1' (+)+fmap n = composeN n $ Prelude.fmap (+1)+fmap' t n = composeN' n $ t . Prelude.fmap (+1)+map n = composeN n $ S.map (+1)+map' t n = composeN' n $ t . S.map (+1)+mapM t n = composeN' n $ t . S.mapM return++{-# INLINE tap #-}+tap :: MonadIO m => Int -> Stream m Int -> m ()+tap n = composeN n $ S.tap FL.sum++{-# INLINE tapRate #-}+tapRate :: Int -> Stream IO Int -> IO ()+tapRate n str = do+ cref <- newIORef 0+ composeN n (Internal.tapRate 1 (\c -> modifyIORef' cref (c +))) str++{-# INLINE pollCounts #-}+pollCounts :: Int -> Stream IO Int -> IO ()+pollCounts n str = do+ composeN n (Internal.pollCounts (P.const P.True) f FL.drain) str+ where f = Internal.rollingMap (P.-) . Internal.delayPost 1++{-# INLINE tapAsyncS #-}+tapAsyncS :: S.MonadAsync m => Int -> Stream m Int -> m ()+tapAsyncS n = composeN n $ Par.tapAsync S.sum++{-# INLINE tapAsync #-}+tapAsync :: S.MonadAsync m => Int -> Stream m Int -> m ()+tapAsync n = composeN n $ Internal.tapAsync FL.sum++mapMaybe n = composeN n $ S.mapMaybe+ (\x -> if Prelude.odd x then Nothing else Just x)+mapMaybeM n = composeN n $ S.mapMaybeM+ (\x -> if Prelude.odd x then return Nothing else return $ Just x)+sequence t = transform . t . S.sequence+filterEven n = composeN n $ S.filter even+filterAllOut value n = composeN n $ S.filter (> (value + 1))+filterAllIn value n = composeN n $ S.filter (<= (value + 1))+takeOne n = composeN n $ S.take 1+takeAll value n = composeN n $ S.take (value + 1)+takeWhileTrue value n = composeN n $ S.takeWhile (<= (value + 1))+takeWhileMTrue value n = composeN n $ S.takeWhileM (return . (<= (value + 1)))+dropOne n = composeN n $ S.drop 1+dropAll value n = composeN n $ S.drop (value + 1)+dropWhileTrue value n = composeN n $ S.dropWhile (<= (value + 1))+dropWhileMTrue value n = composeN n $ S.dropWhileM (return . (<= (value + 1)))+dropWhileFalse value n = composeN n $ S.dropWhile (> (value + 1))+findIndices value n = composeN n $ S.findIndices (== (value + 1))+elemIndices value n = composeN n $ S.elemIndices (value + 1)+intersperse value n = composeN n $ S.intersperse (value + 1)+insertBy value n = composeN n $ S.insertBy compare (value + 1)+deleteBy value n = composeN n $ S.deleteBy (>=) (value + 1)+reverse n = composeN n $ S.reverse+reverse' n = composeN n $ Internal.reverse'+foldrS n = composeN n $ Internal.foldrS S.cons S.nil+foldrSMap n = composeN n $ Internal.foldrS (\x xs -> x + 1 `S.cons` xs) S.nil+foldrT n = composeN n $ Internal.foldrT S.cons S.nil+foldrTMap n = composeN n $ Internal.foldrT (\x xs -> x + 1 `S.cons` xs) S.nil++{-# INLINE takeByTime #-}+takeByTime :: NanoSecond64 -> Int -> Stream IO Int -> IO ()+takeByTime i n = composeN n (Internal.takeByTime i)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'takeByTime+-- inspect $ 'takeByTime `hasNoType` ''D.Step+#endif++{-# INLINE dropByTime #-}+dropByTime :: NanoSecond64 -> Int -> Stream IO Int -> IO ()+dropByTime i n = composeN n (Internal.dropByTime i)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'dropByTime+-- inspect $ 'dropByTime `hasNoType` ''D.Step+#endif++-------------------------------------------------------------------------------+-- Pipes+-------------------------------------------------------------------------------++{-# INLINE transformMapM #-}+{-# INLINE transformComposeMapM #-}+{-# INLINE transformTeeMapM #-}+{-# INLINE transformZipMapM #-}++transformMapM, transformComposeMapM, transformTeeMapM,+ transformZipMapM :: (S.IsStream t, S.MonadAsync m)+ => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()++transformMapM t n = composeN' n $ t . Internal.transform (Pipe.mapM return)+transformComposeMapM t n = composeN' n $ t . Internal.transform+ (Pipe.mapM (\x -> return (x + 1))+ `Pipe.compose` Pipe.mapM (\x -> return (x + 2)))+transformTeeMapM t n = composeN' n $ t . Internal.transform+ (Pipe.mapM (\x -> return (x + 1))+ `Pipe.tee` Pipe.mapM (\x -> return (x + 2)))+transformZipMapM t n = composeN' n $ t . Internal.transform+ (Pipe.zipWith (+) (Pipe.mapM (\x -> return (x + 1)))+ (Pipe.mapM (\x -> return (x + 2))))++-------------------------------------------------------------------------------+-- Mixed Transformation+-------------------------------------------------------------------------------++{-# INLINE scanMap #-}+{-# INLINE dropMap #-}+{-# INLINE dropScan #-}+{-# INLINE takeDrop #-}+{-# INLINE takeScan #-}+{-# INLINE takeMap #-}+{-# INLINE filterDrop #-}+{-# INLINE filterTake #-}+{-# INLINE filterScan #-}+{-# INLINE filterScanl1 #-}+{-# INLINE filterMap #-}+scanMap, dropMap, dropScan,+ filterScan, filterScanl1+ :: MonadIO m => Int -> Stream m Int -> m ()++takeDrop, takeScan, takeMap, filterDrop,+ filterTake, filterMap+ :: MonadIO m => Int -> Int -> Stream m Int -> m ()++scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0+dropMap n = composeN n $ S.map (subtract 1) . S.drop 1+dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1+takeDrop value n = composeN n $ S.drop 1 . S.take (value + 1)+takeScan value n = composeN n $ S.scanl' (+) 0 . S.take (value + 1)+takeMap value n = composeN n $ S.map (subtract 1) . S.take (value + 1)+filterDrop value n = composeN n $ S.drop 1 . S.filter (<= (value + 1))+filterTake value n = composeN n $ S.take (value + 1) . S.filter (<= (value + 1))+filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound)+filterScanl1 n = composeN n $ S.scanl1' (+) . S.filter (<= maxBound)+filterMap value n = composeN n $ S.map (subtract 1) . S.filter (<= (value + 1))++-------------------------------------------------------------------------------+-- Scan and fold+-------------------------------------------------------------------------------++data Pair a b = Pair !a !b deriving (Generic, NFData)++{-# INLINE sumProductFold #-}+sumProductFold :: Monad m => Stream m Int -> m (Int, Int)+sumProductFold = S.foldl' (\(s,p) x -> (s + x, p P.* x)) (0,1)++{-# INLINE sumProductScan #-}+sumProductScan :: Monad m => Stream m Int -> m (Pair Int Int)+sumProductScan = S.foldl' (\(Pair _ p) (s0,x) -> Pair s0 (p P.* x)) (Pair 0 1)+ . S.scanl' (\(s,_) x -> (s + x,x)) (0,0)++-------------------------------------------------------------------------------+-- Iteration+-------------------------------------------------------------------------------++iterStreamLen, maxIters :: Int+iterStreamLen = 10+maxIters = 10000++{-# INLINE iterateSource #-}+iterateSource+ :: S.MonadAsync m+ => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int+iterateSource g i n = f i (sourceUnfoldrMN iterStreamLen n)+ where+ f (0 :: Int) m = g m+ f x m = g (f (x P.- 1) m)++{-# INLINE iterateMapM #-}+{-# INLINE iterateScan #-}+{-# INLINE iterateScanl1 #-}+{-# INLINE iterateFilterEven #-}+{-# INLINE iterateTakeAll #-}+{-# INLINE iterateDropOne #-}+{-# INLINE iterateDropWhileFalse #-}+{-# INLINE iterateDropWhileTrue #-}+iterateMapM, iterateScan, iterateScanl1, iterateFilterEven,+ iterateDropOne+ :: S.MonadAsync m+ => Int -> Stream m Int++iterateTakeAll,+ iterateDropWhileFalse, iterateDropWhileTrue+ :: S.MonadAsync m+ => Int -> Int -> Stream m Int++-- this is quadratic+iterateScan = iterateSource (S.scanl' (+) 0) (maxIters `div` 10)+-- so is this+iterateScanl1 = iterateSource (S.scanl1' (+)) (maxIters `div` 10)++iterateMapM = iterateSource (S.mapM return) maxIters+iterateFilterEven = iterateSource (S.filter even) maxIters+iterateTakeAll value = iterateSource (S.take (value + 1)) maxIters+iterateDropOne = iterateSource (S.drop 1) maxIters+iterateDropWhileFalse value = iterateSource (S.dropWhile (> (value + 1))) maxIters+iterateDropWhileTrue value = iterateSource (S.dropWhile (<= (value + 1))) maxIters++-------------------------------------------------------------------------------+-- Combining streams+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Appending+-------------------------------------------------------------------------------++{-# INLINE serial2 #-}+serial2 :: Int -> Int -> IO ()+serial2 count n =+ S.drain $ S.serial+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++{-# INLINE serial4 #-}+serial4 :: Int -> Int -> IO ()+serial4 count n =+ S.drain $ S.serial+ ((S.serial (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))))+ ((S.serial (sourceUnfoldrMN count (n+2))+ (sourceUnfoldrMN count (n + 3))))++{-# INLINE append2 #-}+append2 :: Int -> Int -> IO ()+append2 count n =+ S.drain $ Internal.append+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++{-# INLINE append4 #-}+append4 :: Int -> Int -> IO ()+append4 count n =+ S.drain $ Internal.append+ ((Internal.append (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))))+ ((Internal.append (sourceUnfoldrMN count (n+2))+ (sourceUnfoldrMN count (n + 3))))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'append2+inspect $ 'append2 `hasNoType` ''D.AppendState+#endif++-------------------------------------------------------------------------------+-- Interleaving+-------------------------------------------------------------------------------++{-# INLINE wSerial2 #-}+wSerial2 :: Int -> Int -> IO ()+wSerial2 value n = S.drain $ S.wSerial+ (sourceUnfoldrMN (value `div` 2) n)+ (sourceUnfoldrMN (value `div` 2) (n + 1))++{-# INLINE interleave2 #-}+interleave2 :: Int -> Int -> IO ()+interleave2 value n = S.drain $ Internal.interleave+ (sourceUnfoldrMN (value `div` 2) n)+ (sourceUnfoldrMN (value `div` 2) (n + 1))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'interleave2+inspect $ 'interleave2 `hasNoType` ''D.InterleaveState+#endif++{-# INLINE roundRobin2 #-}+roundRobin2 :: Int -> Int -> IO ()+roundRobin2 value n = S.drain $ Internal.roundrobin+ (sourceUnfoldrMN (value `div` 2) n)+ (sourceUnfoldrMN (value `div` 2) (n + 1))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'roundRobin2+inspect $ 'roundRobin2 `hasNoType` ''D.InterleaveState+#endif++-------------------------------------------------------------------------------+-- Merging+-------------------------------------------------------------------------------++{-# INLINE mergeBy #-}+mergeBy :: Int -> Int -> IO ()+mergeBy count n =+ S.drain $ S.mergeBy P.compare+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'mergeBy+inspect $ 'mergeBy `hasNoType` ''D.Step+#endif++-------------------------------------------------------------------------------+-- Zipping+-------------------------------------------------------------------------------++{-# INLINE zip #-}+zip :: Int -> Int -> IO ()+zip count n =+ S.drain $ S.zipWith (,)+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'zip+inspect $ 'zip `hasNoType` ''D.Step+#endif++{-# INLINE zipM #-}+zipM :: Int -> Int -> IO ()+zipM count n =+ S.drain $ S.zipWithM (curry return)+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'zipM+inspect $ 'zipM `hasNoType` ''D.Step+#endif++{-# INLINE zipAsync #-}+zipAsync :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m (Int, Int)+zipAsync count n = do+ S.zipAsyncWith (,)+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++{-# INLINE zipAsyncM #-}+zipAsyncM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m (Int, Int)+zipAsyncM count n = do+ S.zipAsyncWithM (curry return)+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++{-# INLINE zipAsyncAp #-}+zipAsyncAp :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m (Int, Int)+zipAsyncAp count n = do+ S.zipAsyncly $ (,)+ <$> (sourceUnfoldrMN count n)+ <*> (sourceUnfoldrMN count (n + 1))++{-# INLINE mergeAsyncByM #-}+mergeAsyncByM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int+mergeAsyncByM count n = do+ S.mergeAsyncByM (\a b -> return (a `compare` b))+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++{-# INLINE mergeAsyncBy #-}+mergeAsyncBy :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int+mergeAsyncBy count n = do+ S.mergeAsyncBy compare+ (sourceUnfoldrMN count n)+ (sourceUnfoldrMN count (n + 1))++-------------------------------------------------------------------------------+-- Multi-stream folds+-------------------------------------------------------------------------------++{-# INLINE isPrefixOf #-}+{-# INLINE isSubsequenceOf #-}+isPrefixOf, isSubsequenceOf :: Monad m => Stream m Int -> m Bool++isPrefixOf src = S.isPrefixOf src src+isSubsequenceOf src = S.isSubsequenceOf src src++{-# INLINE stripPrefix #-}+stripPrefix :: Monad m => Stream m Int -> m ()+stripPrefix src = do+ _ <- S.stripPrefix src src+ return ()++{-# INLINE eqBy' #-}+eqBy' :: (Monad m, P.Eq a) => Stream m a -> m P.Bool+eqBy' src = S.eqBy (==) src src++{-# INLINE eqBy #-}+eqBy :: Int -> Int -> IO Bool+eqBy value n = eqBy' (source value n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'eqBy+inspect $ 'eqBy `hasNoType` ''D.Step+#endif+++{-# INLINE eqByPure #-}+eqByPure :: Int -> Int -> Identity Bool+eqByPure value n = eqBy' (sourceUnfoldr value n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'eqByPure+inspect $ 'eqByPure `hasNoType` ''D.Step+#endif++{-# INLINE cmpBy' #-}+cmpBy' :: (Monad m, P.Ord a) => Stream m a -> m P.Ordering+cmpBy' src = S.cmpBy P.compare src src++{-# INLINE cmpBy #-}+cmpBy :: Int -> Int -> IO P.Ordering+cmpBy value n = cmpBy' (source value n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'cmpBy+inspect $ 'cmpBy `hasNoType` ''D.Step+#endif++{-# INLINE cmpByPure #-}+cmpByPure :: Int -> Int -> Identity P.Ordering+cmpByPure value n = cmpBy' (sourceUnfoldr value n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'cmpByPure+inspect $ 'cmpByPure `hasNoType` ''D.Step+#endif++-------------------------------------------------------------------------------+-- Streams of streams+-------------------------------------------------------------------------------++-- Special cases of concatMap++{-# INLINE sourceFoldMapWith #-}+sourceFoldMapWith :: (S.IsStream t, S.Semigroup (t m Int))+ => Int -> Int -> t m Int+sourceFoldMapWith value n = S.foldMapWith (S.<>) S.yield [n..n+value]++{-# INLINE sourceFoldMapWithM #-}+sourceFoldMapWithM :: (S.IsStream t, Monad m, S.Semigroup (t m Int))+ => Int -> Int -> t m Int+sourceFoldMapWithM value n = S.foldMapWith (S.<>) (S.yieldM . return) [n..n+value]++{-# INLINE sourceFoldMapM #-}+sourceFoldMapM :: (S.IsStream t, Monad m, P.Monoid (t m Int))+ => Int -> Int -> t m Int+sourceFoldMapM value n = F.foldMap (S.yieldM . return) [n..n+value]++{-# INLINE sourceConcatMapId #-}+sourceConcatMapId :: (S.IsStream t, Monad m)+ => Int -> Int -> t m Int+sourceConcatMapId value n =+ S.concatMap P.id $ S.fromFoldable $ P.map (S.yieldM . return) [n..n+value]++-- concatMap unfoldrM/unfoldrM++{-# INLINE concatMap #-}+concatMap :: Int -> Int -> Int -> IO ()+concatMap outer inner n =+ S.drain $ S.concatMap+ (\_ -> sourceUnfoldrMN inner n)+ (sourceUnfoldrMN outer n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMap+#endif++-- concatMap unfoldr/unfoldr++{-# INLINE concatMapPure #-}+concatMapPure :: Int -> Int -> Int -> IO ()+concatMapPure outer inner n =+ S.drain $ S.concatMap+ (\_ -> sourceUnfoldrN inner n)+ (sourceUnfoldrN outer n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMapPure+#endif++-- concatMap replicate/unfoldrM++{-# INLINE concatMapRepl4xN #-}+concatMapRepl4xN :: Int -> Int -> IO ()+concatMapRepl4xN value n = S.drain $ S.concatMap (S.replicate 4)+ (sourceUnfoldrMN (value `div` 4) n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMapRepl4xN+#endif++-- concatMapWith++{-# INLINE concatStreamsWith #-}+concatStreamsWith+ :: (forall c. S.SerialT IO c -> S.SerialT IO c -> S.SerialT IO c)+ -> Int+ -> Int+ -> Int+ -> IO ()+concatStreamsWith op outer inner n =+ S.drain $ S.concatMapWith op+ (\i -> sourceUnfoldrMN inner i)+ (sourceUnfoldrMN outer n)++{-# INLINE concatMapWithSerial #-}+concatMapWithSerial :: Int -> Int -> Int -> IO ()+concatMapWithSerial = concatStreamsWith S.serial++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMapWithSerial+#endif++{-# INLINE concatMapWithAppend #-}+concatMapWithAppend :: Int -> Int -> Int -> IO ()+concatMapWithAppend = concatStreamsWith Internal.append++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMapWithAppend+#endif++{-# INLINE concatMapWithWSerial #-}+concatMapWithWSerial :: Int -> Int -> Int -> IO ()+concatMapWithWSerial = concatStreamsWith S.wSerial++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatMapWithWSerial+#endif++-- concatUnfold++-- concatUnfold replicate/unfoldrM++{-# INLINE concatUnfoldRepl4xN #-}+concatUnfoldRepl4xN :: Int -> Int -> IO ()+concatUnfoldRepl4xN value n =+ S.drain $ S.concatUnfold+ (UF.replicateM 4)+ (sourceUnfoldrMN (value `div` 4) n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatUnfoldRepl4xN+inspect $ 'concatUnfoldRepl4xN `hasNoType` ''D.ConcatMapUState+#endif++{-# INLINE concatUnfoldInterleaveRepl4xN #-}+concatUnfoldInterleaveRepl4xN :: Int -> Int -> IO ()+concatUnfoldInterleaveRepl4xN value n =+ S.drain $ Internal.concatUnfoldInterleave+ (UF.replicateM 4)+ (sourceUnfoldrMN (value `div` 4) n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatUnfoldInterleaveRepl4xN+-- inspect $ 'concatUnfoldInterleaveRepl4xN `hasNoType` ''D.ConcatUnfoldInterleaveState+#endif++{-# INLINE concatUnfoldRoundrobinRepl4xN #-}+concatUnfoldRoundrobinRepl4xN :: Int -> Int -> IO ()+concatUnfoldRoundrobinRepl4xN value n =+ S.drain $ Internal.concatUnfoldRoundrobin+ (UF.replicateM 4)+ (sourceUnfoldrMN (value `div` 4) n)++#ifdef INSPECTION+inspect $ hasNoTypeClasses 'concatUnfoldRoundrobinRepl4xN+-- inspect $ 'concatUnfoldRoundrobinRepl4xN `hasNoType` ''D.ConcatUnfoldInterleaveState+#endif++-------------------------------------------------------------------------------+-- Monad transformation (hoisting etc.)+-------------------------------------------------------------------------------++{-# INLINE sourceUnfoldrState #-}+sourceUnfoldrState :: (S.IsStream t, S.MonadAsync m)+ => Int -> Int -> t (StateT Int m) Int+sourceUnfoldrState value n = S.unfoldrM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else do+ s <- get+ put (s + 1)+ return (Just (s, cnt + 1))++{-# INLINE evalStateT #-}+evalStateT :: S.MonadAsync m => Int -> Int -> Stream m Int+evalStateT value n = Internal.evalStateT 0 (sourceUnfoldrState value n)++{-# INLINE withState #-}+withState :: S.MonadAsync m => Int -> Int -> Stream m Int+withState value n =+ Internal.evalStateT (0 :: Int) (Internal.liftInner (sourceUnfoldrM value n))++-------------------------------------------------------------------------------+-- Concurrent application/fold+-------------------------------------------------------------------------------++{-# INLINE parAppMap #-}+parAppMap :: S.MonadAsync m => Stream m Int -> m ()+parAppMap src = S.drain $ S.map (+1) S.|$ src++{-# INLINE parAppSum #-}+parAppSum :: S.MonadAsync m => Stream m Int -> m ()+parAppSum src = (S.sum S.|$. src) >>= \x -> P.seq x (return ())++-------------------------------------------------------------------------------+-- Type class instances+-------------------------------------------------------------------------------++{-# INLINE eqInstance #-}+eqInstance :: Stream Identity Int -> Bool+eqInstance src = src == src++{-# INLINE eqInstanceNotEq #-}+eqInstanceNotEq :: Stream Identity Int -> Bool+eqInstanceNotEq src = src P./= src++{-# INLINE ordInstance #-}+ordInstance :: Stream Identity Int -> Bool+ordInstance src = src P.< src++{-# INLINE ordInstanceMin #-}+ordInstanceMin :: Stream Identity Int -> Stream Identity Int+ordInstanceMin src = P.min src src++{-# INLINE showInstance #-}+showInstance :: Stream Identity Int -> P.String+showInstance src = P.show src++{-# INLINE showInstanceList #-}+showInstanceList :: [Int] -> P.String+showInstanceList src = P.show src++{-# INLINE readInstance #-}+readInstance :: P.String -> Stream Identity Int+readInstance str =+ let r = P.reads str+ in case r of+ [(x,"")] -> x+ _ -> P.error "readInstance: no parse"++{-# INLINE readInstanceList #-}+readInstanceList :: P.String -> [Int]+readInstanceList str =+ let r = P.reads str+ in case r of+ [(x,"")] -> x+ _ -> P.error "readInstance: no parse"++-------------------------------------------------------------------------------+-- Pure (Identity) streams+-------------------------------------------------------------------------------++{-# INLINE pureFoldl' #-}+pureFoldl' :: Stream Identity Int -> Int+pureFoldl' = runIdentity . S.foldl' (+) 0++-------------------------------------------------------------------------------+-- Foldable Instance+-------------------------------------------------------------------------------++{-# INLINE foldableFoldl' #-}+foldableFoldl' :: Int -> Int -> Int+foldableFoldl' value n =+ F.foldl' (+) 0 (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableFoldrElem #-}+foldableFoldrElem :: Int -> Int -> Bool+foldableFoldrElem value n =+ F.foldr (\x xs -> if x == value then P.True else xs)+ (P.False)+ (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableSum #-}+foldableSum :: Int -> Int -> Int+foldableSum value n =+ P.sum (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableProduct #-}+foldableProduct :: Int -> Int -> Int+foldableProduct value n =+ P.product (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableNull #-}+foldableNull :: Int -> Int -> Bool+foldableNull value n =+ P.null (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableElem #-}+foldableElem :: Int -> Int -> Bool+foldableElem value n =+ P.elem value (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableNotElem #-}+foldableNotElem :: Int -> Int -> Bool+foldableNotElem value n =+ P.notElem value (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableFind #-}+foldableFind :: Int -> Int -> Maybe Int+foldableFind value n =+ F.find (== (value + 1)) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableAll #-}+foldableAll :: Int -> Int -> Bool+foldableAll value n =+ P.all (<= (value + 1)) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableAny #-}+foldableAny :: Int -> Int -> Bool+foldableAny value n =+ P.any (> (value + 1)) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableAnd #-}+foldableAnd :: Int -> Int -> Bool+foldableAnd value n =+ P.and $ S.map (<= (value + 1)) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableOr #-}+foldableOr :: Int -> Int -> Bool+foldableOr value n =+ P.or $ S.map (> (value + 1)) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableLength #-}+foldableLength :: Int -> Int -> Int+foldableLength value n =+ P.length (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableMin #-}+foldableMin :: Int -> Int -> Int+foldableMin value n =+ P.minimum (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableMax #-}+foldableMax :: Int -> Int -> Int+foldableMax value n =+ P.maximum (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableMinBy #-}+foldableMinBy :: Int -> Int -> Int+foldableMinBy value n =+ F.minimumBy compare (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableListMinBy #-}+foldableListMinBy :: Int -> Int -> Int+foldableListMinBy value n = F.minimumBy compare [1..value+n]++{-# INLINE foldableMaxBy #-}+foldableMaxBy :: Int -> Int -> Int+foldableMaxBy value n =+ F.maximumBy compare (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableToList #-}+foldableToList :: Int -> Int -> [Int]+foldableToList value n =+ F.toList (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableMapM_ #-}+foldableMapM_ :: Monad m => Int -> Int -> m ()+foldableMapM_ value n =+ F.mapM_ (\_ -> return ()) (sourceUnfoldr value n :: S.SerialT Identity Int)++{-# INLINE foldableSequence_ #-}+foldableSequence_ :: Int -> Int -> IO ()+foldableSequence_ value n =+ F.sequence_ (sourceUnfoldrAction value n :: S.SerialT Identity (IO Int))++{-# INLINE foldableMsum #-}+foldableMsum :: Int -> Int -> IO Int+foldableMsum value n =+ F.msum (sourceUnfoldrAction value n :: S.SerialT Identity (IO Int))++-------------------------------------------------------------------------------+-- Traversable Instance+-------------------------------------------------------------------------------++{-# INLINE traversableTraverse #-}+traversableTraverse :: Stream Identity Int -> IO (Stream Identity Int)+traversableTraverse = P.traverse return++{-# INLINE traversableSequenceA #-}+traversableSequenceA :: Stream Identity Int -> IO (Stream Identity Int)+traversableSequenceA = P.sequenceA . P.fmap return++{-# INLINE traversableMapM #-}+traversableMapM :: Stream Identity Int -> IO (Stream Identity Int)+traversableMapM = P.mapM return++{-# INLINE traversableSequence #-}+traversableSequence :: Stream Identity Int -> IO (Stream Identity Int)+traversableSequence = P.sequence . P.fmap return
credits/CONTRIBUTORS.md view
@@ -4,21 +4,31 @@ Use `git shortlog -sn tag1...tag2` on the git repository to get a list of contributors between two repository tags. +## 0.7.1++* Harendra Kumar+* Pranay Sashank+* Adithya Kumar+* Sanchayan Maity+* Brian Wignall+* Julian Ospald+* Lucian Ursu+ ## 0.7.0 -Harendra Kumar-Pranay Sashank-Artyom Kazak-David Feuer-Adithya Kumar-Aravind Gopal+* Harendra Kumar+* Pranay Sashank+* Artyom Kazak+* David Feuer+* Adithya Kumar+* Aravind Gopal ## 0.6.1 -Harendra Kumar-Mariusz Ryndzionek-Luke Clifton-Nicolas Henin+* Harendra Kumar+* Mariusz Ryndzionek+* Luke Clifton+* Nicolas Henin ## 0.6.0
credits/COPYRIGHTS.md view
@@ -3,6 +3,15 @@ original or modified code has been included please search for the copyright notices in the individual files. +## 0.7.1++* For compatibility with older GHC versions portions of PrimArray and+ SmallArray code is taken from "primitive" package.+ * Copyright (c) 2009-2012 Roman Leshchinskiy+ * Copyright (c) 2015 Dan Doel+ * https://hackage.haskell.org/package/primitive-0.7.0.0+ * See primitive-0.7.0.0.txt for the original license.+ ## 0.7.0 * Composable folds include code from the "foldl" package.
− credits/pipes-concurrency.txt
@@ -1,24 +0,0 @@-Copyright (c) 2014 Gabriel Gonzalez-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 Gabriel Gonzalez 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.
+ credits/primitive-0.7.0.0.txt view
@@ -0,0 +1,30 @@+Copyright (c) 2008-2009, Roman Leshchinskiy+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 name of the University nor the names of its contributors may be+used to endorse or promote products derived from this software without+specific prior written permission. ++THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF+GLASGOW AND THE 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+UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE 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.+
+ design/dfa-bytes.png view
binary file changed (absent → 18288 bytes)
+ design/dfa-classes.png view
binary file changed (absent → 15413 bytes)
+ design/dfa-rearr.png view
binary file changed (absent → 16619 bytes)
+ design/inlining.md view
@@ -0,0 +1,123 @@+## INLINE Phases++A missing inline or inline in an incorrect GHC simplifier phase can adversely+impact performance. We use three builtin phases of GHC simplifier for inlining+i.e. phase 0, 1 and 2. We have defined them as follows in `inline.h`:++```+#define INLINE_EARLY INLINE [2]+#define INLINE_NORMAL INLINE [1]+#define INLINE_LATE INLINE [0]+```++## Low Level `fromStreamD/toStreamD` Fusion++The combinators in `Streamly.Prelude` are defined in terms of combinators in+`Streamly.Internal.Data.Stream.StreamD` (Direct style streams) or `Streamly.Internal.Data.Stream.StreamK`+(CPS style streams). We convert the stream from `StreamD` to `StreamK`+representation or vice versa in some cases. ++In the first inlining phase (INLINE_EARLY or INLINE) we expand+the combinators in `Streamly.Prelude` into+fromStreamD/fromStreamK/toStreamD/toStreamK and combinators defined in StreamD+or StreamK modules. Once we do that fromStreamD/toStreamD get exposed and we+can apply rewrite rules to rewrite transformations like `fromStreamK .+toStreamK` to `id`. A plain `INLINE` pragma is usually enough on combinators in+`Streamly.Prelude`.++```+{-# RULES "fromStreamK/toStreamK fusion"+ forall s. toStreamK (fromStreamK s) = s #-}+```++Also, we have to prevent fromStreamK and toStreamK themselves from inlining in+this phase so that rewrite rules can be applied on them, therefore, we annotate+these functions with `INLINE_LATE`.++## Fallback Rules++In some cases, if the operation could not fuse we want to use a fallback+rewrite rule in the next phase. For such cases we use the INLINE_EARLY phase+for the first rewrite and the INLINE_NORMAL phase for the fallback rules.++The fallback rules make sure that if we could not fuse the direct style+operations then better use the CPS style operation, because unfused direct+style would have worse performance than the CPS style ops.++```+{-# INLINE_EARLY unfoldr #-}+unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a+unfoldr step seed = fromStreamS (S.unfoldr step seed)+{-# RULES "unfoldr fallback to StreamK" [1]+ forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}+```++## High Level Operation Fusion++Since each high level combinator in `Streamly.Prelude` is wrapped in+`fromStreamD/toStreamD` etc. the combinator fusion cannot work unless we have+removed those and exposed consecutive operations e.g. a `map` followed by+another `map`. Assuming that redundant `fromStreamK/toStreamK` have been+removed in the `INLINE_EARLY` phase, we can then apply the combinator fusion+rules in the `INLINE_NORMAL` phase. For example, we can fuse two `map`+operations into a single `map` operation. Note that now we have exposed the+`StreamD/StreamK` implementations of combinators and the rules would apply on+those.++## Inlining Higher Order Functions++Note that partially applied functions cannot be inlined. So if we have a code+like this:++```+concatMap1 src = runStream $ S.concatMap (S.replicate 3) src+```++We want to ensure that `concatMap` gets inlined before `replicate` so that+`replicate` becomes fully applied before it gets inlined. Currently ensuring+that both of them are inlined in the same phase (`INLINE_NORMAL`) seems to be+enough to achieve that. In general, we should try to ensure that higher order+functions are inlined before or in the same phase as the functions they can+consume as arguments. This means `StreamD` combinators should not be marked+as `INLINE` or `INLINE_EARLY`, instead they should all be marked as+`INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`+etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like+`Streamly.Memory.Array` should also follow the same rules.++## Stream Fusion++In StreamD combinators, inlining the inner step or loop functions too early+i.e. in the same pahse or before the outer function is inlined may block stream+fusion opportunities. Therefore, the inner step functions and folding loops are+marked as INLINE_LATE.++## Specialization++In some cases, the `step` function in `StreamD` does not get specialized when+inlined unless it is provided with an explicit signature or made a lambda, for+example, in the `replicate/replicateM` combinator we need the type annotation+on `i` to get it specialized:++```+ {-# INLINE_LATE step #-}+ step _ (i :: Int) =+ if i <= 0+ then return Stop+ else do+ x <- action+ return $ Yield x (i - 1)+```++`-flate-specialise` also helps in this case.++## Stream and Fold State Data Structures++Since state is an internal data structure threaded around in the loop, it is a+good practice to use strict unboxed fields for state data structures where+possible. In most cases it is not necessary, but in some cases it may affect+fusion and make a difference of 10x performance or more. For example, using+non-strict fields can increase the code size for internal join points and+functions created during transformations, which can affect the inlining of+these code blocks which in turn can affect stream fusion. ++See https://gitlab.haskell.org/ghc/ghc/issues/17075 .
+ design/linked-lists.md view
@@ -0,0 +1,32 @@+# Linked Lists++The immutable Haskell lists or streams are great for stream processing.+However, they may not be suitable for purposes where we need to store data for+a longer while. In such cases we need mutable linked lists in pinned memory for+high performance applications i.e. we need the C like linked lists. Here are+some cases where linked-lists may be warranted instead of immutable lists:++* Let's say we want to buffer incoming data in a list. The buffered data may be+ millions of elements. When we are buffering we may allocate cells from+ different areas of the GC heap. When there are other activities going on we+ may have to keep copying this buffered data during GCs. When we consume this+ buffer, again it creates a fragmented heap and we may have to copy some other+ long-lived data to defragment the heap. The point is that we should not have+ long-lived data in the GC heap.++* When we delete a node in the list, Haskell lists have to be recreated+ generating a lot of garbage. We cannot take a reference to the unmodified+ segments and reuse them in the new list. On the other hand with mutable+ linked-lists we can delete a node cheaply. This could be a common case in a+ hash table collision chain which requires deletion of elements.++* Similar to deletion, if we need to insert an element in the middle of the+ list, an immutable list has to be re-created.++* To implement a queue, two lists in the immutable model can be used+ efficiently if we are strictly adding at the end and deleting from the front+ and if there is sufficient batching so that swapping of the lists is not a+ common operation. If we have to insert elements in the middle or if we have+ to swap too many times again we will have the same GC issues as stated above.+ For example, in implementations of priority search queues or timer wheels we+ have to mutate the lists.
+ design/module-organization.md view
@@ -0,0 +1,134 @@+# Internal vs External Modules++We keep all modules exposed to facilitate convenient exposure of experimental+APIs and constructors to users. It allows users of the library to experiment+much more easily and carry a caveat that these APIs can change in future+without notice. Since everything is exposed, maintainers do not have to think+about what to expose as experimental and what remains completely hidden every+time something is added to the library.++We expose the internal modules via `Streamly.Internal` namespace to keep all+the internal modules together under one module tree and to have their+documentation also separated under one head in haddock docs.++Another decision point is about two choices:++1) Keep the implementation of all the APIs in an internal module and just+reexport selected APIs through the external module. The disadvantages of this+are:+a) users may not be able to easily figure out what unexposed APIs are available+other than the ones exposed through the external module. To avoid this problem+we can mark the unexposed APIs in the docs with a special comment.+b) In tests and benchmarks we will be using internal modules to test internal+and unexposed APIs. Since exposed APIs are exported via both internal and+external modules we will have to be careful in not using the internal module+for testing accidentally, instead we should always be using the exposed module+so that we are always testing exactly the way users will be using the APIs.++2) Keep the implementations of unexposed modules in the internal module file+and exposed module in the external module file. In this approach, users can+easily figure out the unexposed vs exposed APIs. But maintaining this would+require us to move the APIs from internal to external module file whenever we+expose an API.++We choose the first approach.++# Module Name Spaces++We use the "Streamly" prefix to all the module names so that they do not+conflict with any other module on Hackage.++We have the following module hierarchy under Streamly:++* Data: All the data structures that make use of the unpinned GC memory to+ store data. These data structures are suitable for stream processing but+ may not be suitable for storing large amounts of data in memory for longer+ durations. These are suitable for short lived and smaller structures+ because they can be moved by the GC to defragment the heap.++* Memory: This name space is for data structures that make use of the memory as+ a persistent storage device. The memory may be allocated by foreign+ allocators or pinned memory allocated by GHC. Because the memory is pinned it+ can be used for interfacing with the system/kernel. These structures are+ efficient for storing large amounts of data for longer durations because it+ does not have to be copied by the GC. These structures may not be suitable+ for small, short lived data because that is likely to fragment the heap.++* FileSystem: This name space is for data structures that reside in files+ provided by a file system interface on top of storage devices.++* Network: This name space is for APIs that access data from remote computers+ over the network.++## Data and Memory++As explained above, we distinguish two types of data structures under "Data"+and "Memory". Alternatively, we could have used a "Memory" namespace under+each data structure e.g. "Streamly.Data.Array.Memory" instead of using a top+level "Streamly.Memory", however, we chose to distinguish such data structures+using a top level "Memory" name space because it enforces consistent naming by+fitting all such data structures under this top level hierarchy. It also makes+it easier to find out what all data structures fall in this category.++# Module Types and Naming++## Abstract modules++Abstract modules are meant to represent an abstract interface (e.g. a type+class). Concrete modules can make use of this interface and possibly+extend it to provide concrete functionality.++The general convention in the Haskell ecosystem for naming an abstract+interface module is to name it as "Module.Class" (e.g. Control.Monad.IO.Class).+An alternative name could be "Module.Interface".++In some other cases such modules are named after the class name (e.g. see the+array package for an example). This is more appropriate when there is no single+hierarchy where we can place the ".Class" module. For example, we have arrays+in Data.Array, Memory.Array, we have to choose one over the other to place the+".Class" module for an array abstraction. Alternatively, we can choose+"Data.IsArray" instead.++Yet another way could be to use the parent module as an interface module and+the child modules as concrete modules. For example, "Streamly.Data.Stream"+module could provide the common "Stream" type and the "IsStream" type class.+The submodule "Streamly.Data.Stream.Serial" can provide a concrete "Serial"+stream type importing the "Streamly.Data.Stream" abstract module.++## Common Modules++Some modules represent common types or utility functions that are shared across+multiple modules. The general convention is to name such modules as+"Module.Types", "Module.Common", or "Module.Core".++## Constrained Modules++Some modules represent operations on a type which constrain a type using a type+class or a specific instance of a general type. For example, we may have a+module representing operations on a stream of any type and another module that+specifically deals with operations on a Char stream. There are two ways to deal+with this.++First is to use a submodule for the constrained type. For example,+`Streamly.Data.Stream` represents a general stream type whereas+`Streamly.Data.Stream.Char` represents operations on a stream of Char type.+This makes sense where the type we are constraining to is a specific type+rather than a type constrained using a type class.++Second is to use a separate hierarchy for the constrained type. For example, we+could use `Streamly.Data.Array` for a general array and `Streamly.Prim.Array`+for an array that works on `Prim` types. This makes sense when the type is+constrained by a type class, we may have more data structures for that+constrained type to be bundled under that hierarchy.++## Aggregate modules++In some cases we may want to aggregate the functionality of several small+modules in a combined aggregate module. In many cases, the aggregate module is+made a parent module of the constituent modules. The parent module depends on+the child modules and exposes the functionality from the constituent modules.++## Placeholder Modules++In some cases a parent module is just a placeholder in the namespace and does+not export any functionality.
+ design/utf8-decoder.md view
@@ -0,0 +1,603 @@+Flexible and Economical UTF-8 Decoder+=====================================++Systems with elaborate Unicode support usually confront programmers with+a multitude of different functions and macros to process UTF-8 encoded+strings, often with different ideas on handling buffer boundaries, state+between calls, error conditions, and performance characteristics, making+them difficult to use correctly and efficiently. Implementations also+tend to be very long and complicated; one popular library has over 500+lines of code just for one version of the decoder. This page presents+one that is very easy to use correctly, short, small, fast, and free.++Implementation in C (C99)+-------------------------++ // Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>+ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.++ #define UTF8_ACCEPT 0+ #define UTF8_REJECT 1++ static const uint8_t utf8d[] = {+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df+ 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef+ 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff+ 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2+ 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4+ 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6+ 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8+ };++ uint32_t inline+ decode(uint32_t* state, uint32_t* codep, uint32_t byte) {+ uint32_t type = utf8d[byte];++ *codep = (*state != UTF8_ACCEPT) ?+ (byte & 0x3fu) | (*codep << 6) :+ (0xff >> type) & (byte);++ *state = utf8d[256 + *state*16 + type];+ return *state;+ }++Usage+-----++UTF-8 is a variable length character encoding. To decode a character one+or more bytes have to be read from a string. The `decode` function+implements a single step in this process. It takes two parameters+maintaining state and a byte, and returns the state achieved after+processing the byte. Specifically, it returns the value `UTF8_ACCEPT`+(0) if enough bytes have been read for a character, `UTF8_REJECT` (1) if+the byte is not allowed to occur at its position, and some other+positive value if more bytes have to be read.++When decoding the first byte of a string, the caller must set the state+variable to `UTF8_ACCEPT`. If, after decoding one or more bytes the+state `UTF8_ACCEPT` is reached again, then the decoded Unicode character+value is available through the `codep` parameter. If the state+`UTF8_REJECT` is entered, that state will never be exited unless the+caller intervenes. See the examples below for more information on usage+and error handling, and the section on implementation details for how+the decoder is constructed.++Examples+--------++### Validating and counting characters++This function checks if a null-terminated string is a well-formed UTF-8+sequence and counts how many code points are in the string.++ int+ countCodePoints(uint8_t* s, size_t* count) {+ uint32_t codepoint;+ uint32_t state = 0;++ for (*count = 0; *s; ++s)+ if (!decode(&state, &codepoint, *s))+ *count += 1;++ return state != UTF8_ACCEPT;+ }++It could be used like so:++ if (countCodePoints(s, &count)) {+ printf("The string is malformed\n");+ } else {+ printf("The string is %u characters long\n", count);+ }++### Printing code point values++This function prints out all code points in the string and an error+message if unexpected bytes are encountered, or if the string ends with+an incomplete sequence.++ void+ printCodePoints(uint8_t* s) {+ uint32_t codepoint;+ uint32_t state = 0;++ for (; *s; ++s)+ if (!decode(&state, &codepoint, *s))+ printf("U+%04X\n", codepoint);++ if (state != UTF8_ACCEPT)+ printf("The string is not well-formed\n");++ }++### Printing UTF-16 code units++This loop prints out UTF-16 code units for the characters in a+null-terminated UTF-8 encoded string.++ for (; *s; ++s) {++ if (decode(&state, &codepoint, *s))+ continue;++ if (codepoint <= 0xFFFF) {+ printf("0x%04X\n", codepoint);+ continue;+ }++ // Encode code points above U+FFFF as surrogate pair.+ printf("0x%04X\n", (0xD7C0 + (codepoint >> 10)));+ printf("0x%04X\n", (0xDC00 + (codepoint & 0x3FF)));+ }++### Error recovery++It is sometimes desirable to recover from errors when decoding strings+that are supposed to be UTF-8 encoded. Programmers should be aware that+this can negatively affect the security properties of their application.+A common recovery method is to replace malformed sequences with a+substitute character like `U+FFFD REPLACEMENT CHARACTER`.++Decoder implementations differ in which octets they replace and where+they restart. Consider for instance the sequence `0xED 0xA0 0x80`. It+encodes a surrogate code point which is prohibited in UTF-8. A+recovering decoder may replace the whole sequence and restart with the+next byte, or it may replace the first byte and restart with the second+byte, replace it, restart with the third, and replace the third byte+aswell.++The following code implements one such recovery strategy. When an+unexpected byte is encountered, the sequence up to that point will be+replaced and, if the error occurred in the middle of a sequence, will+retry the byte as if it occurred at the beginning of a string. Note that+the decode function detects errors as early as possible, so the sequence+`0xED 0xA0 0x80` would result in three replacement characters.++ for (prev = 0, current = 0; *s; prev = current, ++s) {++ switch (decode(¤t, &codepoint, *s)) {+ case UTF8_ACCEPT:+ // A properly encoded character has been found.+ printf("U+%04X\n", codepoint);+ break;++ case UTF8_REJECT:+ // The byte is invalid, replace it and restart.+ printf("U+FFFD (Bad UTF-8 sequence)\n");+ current = UTF8_ACCEPT;+ if (prev != UTF8_ACCEPT)+ s--;+ break;+ ...++For some recovery strategies it may be useful to determine the number of+bytes expected. The states in the automaton are numbered such that,+assuming C\'s division operator, `state / 3 + 1` is that number. Of+course, this will only work for states other than `UTF8_ACCEPT` and+`UTF8_REJECT`. This number could then be used, for instance, to skip the+continuation octets in the illegal sequence `0xED 0xA0 0x80` so it will+be replaced by a single replacement character.++### Transcoding to UTF-16 buffer++This is a rough outline of a UTF-16 transcoder. Actual applications+would add code for error reporting, reporting of words written, required+buffer size in the case of a small buffer, and possibly other things.+Note that in order to avoid checking for free space in the inner loop,+we determine how many bytes can be read without running out of space.+This is one utf-8 byte per available utf-16 word, with one exception: if+the last byte read was the third byte in a four byte sequence we would+get two words for the next byte; so we read one byte less than we have+words available. This additional word is also needed for+null-termination, so it\'s never wrong to read one less.++ int+ toUtf16(uint8_t* src, size_t srcBytes, uint16_t* dst, size_t dstWords, ...) {++ uint8_t* src_actual_end = src + srcBytes;+ uint8_t* s = src;+ uint16_t* d = dst;+ uint32_t codepoint;+ uint32_t state = 0;++ while (s < src_actual_end) {++ size_t dst_words_free = dstWords - (d - dst);+ uint8_t* src_current_end = s + dst_words_free - 1;++ if (src_actual_end < src_current_end)+ src_current_end = src_actual_end;++ if (src_current_end <= s)+ goto toosmall;++ while (s < src_current_end) {++ if (decode(&state, &codepoint, *s++))+ continue;++ if (codepoint > 0xffff) {+ *d++ = (uint16_t)(0xD7C0 + (codepoint >> 10));+ *d++ = (uint16_t)(0xDC00 + (codepoint & 0x3FF));+ } else {+ *d++ = (uint16_t)codepoint;+ }+ }+ }++ if (state != UTF8_ACCEPT) {+ ...+ }++ if ((dstWords - (d - dst)) == 0)+ goto toosmall;++ *d++ = 0;+ ...++ toosmall:+ ...+ }++Implementation details+----------------------++The `utf8d` table consists of two parts. The first part maps bytes to+character classes, the second part encodes a deterministic finite+automaton using these character classes as transitions. This section+details the composition of the table.++### Canonical UTF-8 automaton++UTF-8 is a variable length character encoding. That means state has to+be maintained while processing a string. The following transition graph+illustrates the process. We start in state zero, and whenever we come+back to it, we\'ve seen a whole Unicode character. Transitions not in+the graph are disallowed; they all lead to state one, which has been+omitted for readability.++++### Automaton with character class transitions++The byte ranges in the transition graph above are not easily encoded in+the automaton in a manner that would allow fast lookup. Instead of+encoding the ranges directly, the ranges are split such that each byte+belongs to exactly one character class. Then the transitions go over+these character classes.++++### Mapping bytes to character classes++Primarily to save space in the transition table, bytes are mapped to+character classes. This is the mapping:++|||||+|-|-|-|-|+| 00..7f | 0 | 80..8f | 1 |+| 90..9f | 9 | a0..bf | 7 |+| c0..c1 | 8 | c2..df | 2 |+| e0..e0 | 10 | e1..ec | 3 |+| ed..ed | 4 | ee..ef | 3 |+| f0..f0 | 11 | f1..f3 | 6 |+| f4..f4 | 5 | f5..ff | 8 |+++For bytes that may occur at the beginning of a multibyte sequence, the+character class number is also used to remove the most significant bits+from the byte, which do not contribute to the actual code point value.+Note that `0xc0`, `0xc1`, and `0xf5` .. `0xff` have all their bits+removed. These bytes cannot occur in well-formed sequences, so it does+not matter which bits, if any, are retained.++|||||||||||||+|-|-|-|-|-|-|-|-|-|-|-|-|+| c0 | 8 | **11000000** | d0 | 2 | **11**010000 | e0 | 10 | **11100000** | f0 | 11 | **11110000** |+| c1 | 8 | **11000001** | d1 | 2 | **11**010001 | e1 | 3 | **111**00001 | f1 | 6 | **111100**01 |+| c2 | 2 | **11**000010 | d2 | 2 | **11**010010 | e2 | 3 | **111**00010 | f2 | 6 | **111100**10 |+| c3 | 2 | **11**000011 | d3 | 2 | **11**010011 | e3 | 3 | **111**00011 | f3 | 6 | **111100**11 |+| c4 | 2 | **11**000100 | d4 | 2 | **11**010100 | e4 | 3 | **111**00100 | f4 | 5 | **11110**100 |+| c5 | 2 | **11**000101 | d5 | 2 | **11**010101 | e5 | 3 | **111**00101 | f5 | 8 | **11110101** |+| c6 | 2 | **11**000110 | d6 | 2 | **11**010110 | e6 | 3 | **111**00110 | f6 | 8 | **11110110** |+| c7 | 2 | **11**000111 | d7 | 2 | **11**010111 | e7 | 3 | **111**00111 | f7 | 8 | **11110111** |+| c8 | 2 | **11**001000 | d8 | 2 | **11**011000 | e8 | 3 | **111**01000 | f8 | 8 | **11111000** |+| c9 | 2 | **11**001001 | d9 | 2 | **11**011001 | e9 | 3 | **111**01001 | f9 | 8 | **11111001** |+| ca | 2 | **11**001010 | da | 2 | **11**011010 | ea | 3 | **111**01010 | fa | 8 | **11111010** |+| cb | 2 | **11**001011 | db | 2 | **11**011011 | eb | 3 | **111**01011 | fb | 8 | **11111011** |+| cc | 2 | **11**001100 | dc | 2 | **11**011100 | ec | 3 | **111**01100 | fc | 8 | **11111100** |+| cd | 2 | **11**001101 | dd | 2 | **11**011101 | ed | 4 | **1110**1101 | fd | 8 | **11111101** |+| ce | 2 | **11**001110 | de | 2 | **11**011110 | ee | 3 | **111**01110 | fe | 8 | **11111110** |+| cf | 2 | **11**001111 | df | 2 | **11**011111 | ef | 3 | **111**01111 | ff | 8 | **11111111** |+++Notes on Variations+-------------------++There are several ways to change the implementation of this decoder. For+example, the size of the data table can be reduced, at the cost of a+couple more instructions, so it omits the mapping of bytes in the+US-ASCII range, and since all entries in the table are 4 bit values, two+values could be stored in a single byte.++In some situations it may be beneficial to have a separate start state.+This is easily achieved by copying the s0 state in the array to the end,+and using the new state 9 as start state as needed.++Where callers require the code point values, compilers tend to generate+slightly better code if the state calculation is moved into the+branches, for example++ if (*state != UTF8_ACCEPT) {+ *state = utf8d[256 + *state*16 + type];+ *codep = (*codep << 6) | (byte & 63);+ } else {+ *state = utf8d[256 + *state*16 + type];+ *codep = (byte) & (255 >> type);+ }++As the state will be zero in the else branch, this saves a shift and an+addition for each starter. Unfortunately, compilers will then typically+generate worse code if the codepoint value is not needed. Naturally,+then, two functions could be used, one that only calculates the states+for validation, counting, and similar applications, and one for full+decoding. For the sample UTF-16 transcoder a more substantial increase+in performance can be achieved by manually including the decode code in+the inner loop; then it is also worthwhile to make code points in the+US-ASCII range a special case:++ while (s < src_current_end) {++ uint32_t byte = *s++;+ uint32_t type = utf8d[byte];++ if (state != UTF8_ACCEPT) {+ codep = (codep << 6) | (byte & 63);+ state = utf8d[256 + state*16 + type];++ if (state)+ continue;++ } else if (byte > 0x7f) {+ codep = (byte) & (255 >> type);+ state = utf8d[256 + type];+ continue;++ } else {+ *d++ = (uint16_t)byte;+ continue;+ }+ ...++Another variation worth of note is changing the comparison when setting+the code point value to this:++ *codep = (*state > UTF8_REJECT) ?+ (byte & 0x3fu) | (*codep << 6) :+ (0xff >> type) & (byte);++This ensures that the code point value does not exceed the value `0xff`+after some malformed sequence is encountered.++As written, the decoder disallows encoding of surrogate code points,+overlong 2, 3, and 4 byte sequences, and 4 byte sequences outside the+Unicode range. Allowing them can have serious security implications, but+can easily be achieved by changing the character class assignments in+the table.++The code samples have generally been written to perform well on my+system when compiled with Visual C++ 7.1 and GCC 3.4.5. Slight changes+may improve performance, for example, Visual C++ 7.1 will produce+slightly faster code when, in the manually inlined version of the+transcoder discussed above, the type assignment is moved into the+branches where it is needed, and the state and codepoint assignments in+the non-ASCII starter is swapped (approximately a 5% increase), but GCC+3.4.5 will produce considerably slower code (approximately 10%).++I have experimented with various rearrangements of states and character+classes. A seemingly promising one is the following:++++One of the continuation ranges has been split into two, the other+changes are just renamings. This arrangement allows, when a continuation+octet is expected, to compute the character class with a shift instead+of a table lookup, and when looking at a non-ASCII starter, the next+state is simply the character class. On my system the change in+performance is in the area of +/- 1%. This encoding would have a number+of downsides: more rejecting states are required to account for+continuation octets where starters are expected, the table formatting+would use more hex notation making it longer, and calculating the number+of expected continuation octets from a given state is more difficult.+One thing I\'d still like to try out is if, perhaps by adding a couple+of additional states, for continuation states the next state can be+computed without any table lookup with a few easily paired instructions.++On 24th June 2010 Rich Felker pointed out that the state values in the+transition table can be pre-multiplied with 16 which would save a shift+instruction for every byte. D\'oh! We actually just need 12 and can+throw away the filler values previously in the table making the table 36+bytes shorter and save the shift in the code.++ // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>+ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.++ #define UTF8_ACCEPT 0+ #define UTF8_REJECT 12++ static const uint8_t utf8d[] = {+ // The first part of the table maps bytes to character classes that+ // to reduce the size of the transition table and create bitmasks.+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,++ // The second part is a transition table that maps a combination+ // of a state of the automaton and a character class to a state.+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,+ 12,36,12,12,12,12,12,12,12,12,12,12,+ };++ uint32_t inline+ decode(uint32_t* state, uint32_t* codep, uint32_t byte) {+ uint32_t type = utf8d[byte];++ *codep = (*state != UTF8_ACCEPT) ?+ (byte & 0x3fu) | (*codep << 6) :+ (0xff >> type) & (byte);++ *state = utf8d[256 + *state + type];+ return *state;+ }++Notes on performance+--------------------++To conduct some ad-hoc performance testing I\'ve used three different+UTF-8 encoded buffers and passed them through a couple of UTF-8 to+UTF-16 transcoders. The large buffer is a April 2009 Hindi Wikipedia+article XML dump, the medium buffer Markus Kuhn\'s UTF-8-demo.txt, and+the tiny buffer my name, each about the number of times required for+about 1GB of data. All tests ran on a [Intel Prescott+Celeron](http://en.wikipedia.org/wiki/Celeron#Prescott-256) at 2666 MHz.+See [Changes](#changes) for some additional details.++ | | Large | Medium | Tiny |+ |---------------------------------------------------------------------------|---------| ---------|----------|+ |`NS_CStringToUTF16()` Mozilla 1.9 (*includes malloc/free time*) | 36924ms| 39773ms| 107958ms|+ |`iconv()` 1.9 compiled with Visual C++ (Cygwin iconv 1.11 similar) | 22740ms| 21765ms| 32595ms|+ |`g_utf8_to_utf16()` Cygwin Glib 2.0 (*includes malloc/free time*) | 21599ms| 20345ms| 98782ms|+ |`ConvertUTF8toUTF16()` Unicode Inc., Visual C++ 7.1 -Ox -Ot -G7 | 11183ms| 11251ms| 19453ms|+ |`MultiByteToWideChar()` Windows API (Server 2003 SP2) | 9857ms| 10779ms| 12771ms|+ |`u_strFromUTF8` from ICU 4.0.1 (Visual Studio 2008, web site distribution) | 8778ms| 5223ms| 5419ms|+ |`PyUnicode_DecodeUTF8Stateful` (3.1a2), Visual C++ 7.1 -Ox -Ot -G7 | 4523ms| 5686ms| 3138ms|+ |Example section transcoder, Visual C++ 7.1 -Ox -Ot -G7 | 5397ms| 5789ms| 6250ms|+ |Manually inlined transcoder (see above), Visual C++ 7.1 -Ox -Ot -G7 | 4277ms| 4998ms| 4640ms|+ |Same, Cygwin GCC 3.4.5 -march=prescott -fomit-frame-pointer -O3 | 4492ms| 5154ms| 4432ms|+ |Same, Cygwin GCC 4.3.2 -march=prescott -fomit-frame-pointer -O3 | 5439ms| 6322ms| 5567ms|+ |Same, Visual C++ 6.0 -TP -O2 | 5398ms| 6259ms| 6446ms|+ |Same, Visual C++ 7.1 -Ox -Ot -G7 (*includes malloc/free time*) | 5498ms| 5086ms| 25852ms|++I have also timed functions that `xor` all code points in the large+buffer. In Visual Studio 2008 ICU\'s `U8_NEXT` macro comes out at+\~8000ms, the `U8_NEXT_UNSAFE` macro, which requires complete and+well-formed input, at \~4000ms, and the `decode` function is at+\~5900ms. Using the same manual inlining as for the transcode function,+Cygwin GCC 3.4.5 -march=prescott -O3 -fomit-frame-pointer brings it down+to roughly the same times as the transcode function for all three+buffers.++While these results do not model real-world applications well, it seems+reasonable to suggest that the reduced complexity does not come at the+price of reduced performance. Note that instructions that compute the+code point values will generally be optimized away when not needed. For+example, checking if a null-terminated string is properly UTF-8 encoded+\...++ int+ IsUTF8(uint8_t* s) {+ uint32_t codepoint, state = 0;++ while (*s)+ decode(&state, &codepoint, *s++);++ return state == UTF8_ACCEPT;+ }++\... does not require the individual code point values, and so the loop+becomes something like this:++ l1: movzx eax,al+ shl edx,4+ add ecx,1+ movzx eax,byte ptr [eax+404000h]+ movzx edx,byte ptr [eax+edx+256+404000h]+ movzx eax,byte ptr [ecx]+ test al,al+ jne l1++For comparison, this is a typical `strlen` loop:++ l1: mov cl,byte ptr [eax]+ add eax,1+ test cl,cl+ jne l1++With the large buffer and the same number of times as above, `strlen`+takes 1507ms while `IsUTF8` takes 2514ms.++License+-------++Copyright (c) 2008-2009 [Bjoern Hoehrmann](http://bjoern.hoehrmann.de/)+\<<bjoern@hoehrmann.de>\>++Permission is hereby granted, free of charge, to any person obtaining a+copy of this software and associated documentation files (the+\"Software\"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+:::++Changes+-------++25 Jun 2010+: Added an improved variation based on an observation from Rich+ Felker.++30 April 2009+: Added some more items to the performance table: the manually inlined+ transcoder allocating worst case memory for each run and freeing it+ before the next run; and results for Mozilla\'s NS\_CStringToUTF16+ (a new nsAutoString is created for each run, and truncated before+ the next). This used the XULRunner SDK 1.9.0.7 binary distribution+ from the Mozilla website.++18 April 2009+: Added notes to the Variations section on handling malformed+ sequences and failed optimization attempts.++14 April 2009+: Added PyUnicode\_DecodeUTF8Stateful times; the function has been+ modified slightly so it works outside Python and so it uses a+ pre-allocated buffer. Normally does not check output buffer+ boundaries but rather allocates a worst case buffer, then resizes+ it. Apparently the decoder [allows encodings of surrogate code+ points](http://bugs.python.org/issue3672).++Author+------++[Björn Höhrmann](http://bjoern.hoehrmann.de) <bjoern@hoehrmann.de>+([Donate via+SourceForge](http://sourceforge.net/developer/user_donations.php?user_id=188003),+[PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=bjoern@hoehrmann.de&item_name=Support+Bjoern+Hoehrmann))
+ docs/Build.md view
@@ -0,0 +1,91 @@+# Compilation Options++## Recommended Options++Benchmarks show that GHC 8.8 has significantly better performance than GHC 8.6+in many cases.++Use the following GHC options:++```+ -O2 + -fdicts-strict + -fspec-constr-recursive=16 + -fmax-worker-args=16+```++## Using Fusion Plugin++In many cases `fusion-plugin` can improve performance by better stream+fusion. However, in some cases performance may also regress. Please note+that the `fusion-plugin` package works only for GHC >= 8.6.++* Install the+[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin)+package or add it to the `build-depends` section of your program in the+cabal file.++* Use the following GHC option in addition to the options listed in the+ previous section:++```+ -fplugin=Fusion.Plugin +```++## Minimal++At the very least `-O -fdicts-strict` compilation options are+required. If these options are not used, the program may exhibit memory+hog. For example, the following program, if compiled without an+optimization option, is known to hog memory:++```+main = S.drain $ S.concatMap S.fromList $ S.repeat []+```++## Explanation++* `-fdicts-strict` is needed to avoid [a GHC+issue](https://gitlab.haskell.org/ghc/ghc/issues/17745) leading to+memory leak in some cases.++* `-fspec-constr-recursive` is needed for better stream fusion by enabling+the `SpecConstr` optimization in more cases.++* `-fmax-worker-args` is needed for better stream fusion by enabling the+`SpecConstr` optimization in some important cases.++* `-fplugin=Fusion.Plugin` enables predictable stream fusion+optimization in certain cases by helping the compiler inline internal+bindings and therefore enabling case-of-case optimization. In some+cases, especially in some fileio benchmarks, it can make a difference of+5-10x better performance.++# Multi-core Parallelism++Concurrency without a threaded runtime may be a bit more efficient. Do not use+threaded runtime unless you really need multi-core parallelism. To get+multi-core parallelism use the following GHC options:++ `-threaded -with-rtsopts "-N"`++# Compiler Versions++Use GHC 8.8 for best performance.++GHC 8.2.2 may hog memory and hang when building certain application using+streamly (particularly the benchmark programs in the streamly package).+Therefore we recommend avoiding using the GHC version 8.2.x.++# Performance Optimizations++If performance is below expectations:++* Look for inlining functions in the fast path+* Strictness annotations on data, specially the data used as accumulator in+ folds and scans, can help in improving performance.+* Strictness annotations on function arguments can help the compiler unbox+ constructors in certain cases, improving performance.+* Sometimes using `-XStrict` extension can help improve performance, if so you+ may be missing some strictness annotations.+* Use tail recursion for streaming data or for large loops
examples/ControlFlow.hs view
@@ -69,7 +69,7 @@ -- of non-determinism below. -- -- Note that this is redundant configuration as the same behavior can be--- acheived with just streamly, using mzero.+-- achieved with just streamly, using mzero. -- getSequenceMaybeAbove :: (IsStream t, MonadIO (t m)) => MaybeT (t m) () getSequenceMaybeAbove = do
examples/HandleIO.hs view
@@ -1,55 +1,80 @@-import qualified Streamly.Prelude as S+import Data.Char (ord)+import System.Environment (getArgs)+import System.IO (IOMode(..), hSeek, SeekMode(..))+ import qualified Streamly.Data.Fold as FL--- import qualified Streamly.Memory.Array as A import qualified Streamly.FileSystem.Handle as FH import qualified System.IO as FH+import qualified Streamly.Memory.Array as A+import qualified Streamly.Prelude as S -- import qualified Streamly.FileSystem.FD as FH--- import qualified Streamly.Data.Unicode.Stream as US import qualified Streamly.Internal.Data.Fold as FL+import qualified Streamly.Internal.Data.Unicode.Stream as US import qualified Streamly.Internal.Memory.ArrayStream as AS-import qualified Streamly.Internal.FileSystem.Handle as IFH+import qualified Streamly.Internal.Prelude as S -import Data.Char (ord)-import System.Environment (getArgs)-import System.IO (IOMode(..), hSeek, SeekMode(..))+-- Read the contents of a file to stdout.+--+-- FH.read reads the file in 32KB chunks and converts the chunks into a byte+-- stream. FH.write takes the byte stream as input, converts it into chunks of+-- 32KB and writes those chunks to stdout.+--+_cat :: FH.Handle -> IO ()+_cat src = S.fold (FH.write FH.stdout) $ S.unfold FH.read src +-- Chunked version, more efficient than the byte stream version above. Reads+-- the file in 256KB chunks and writes those chunks to stdout. cat :: FH.Handle -> IO () cat src = S.fold (FH.writeChunks FH.stdout)- $ IFH.toChunksWithBufferOf (256*1024) src--- byte stream version--- cat src = S.fold (FH.write FH.stdout) $ FH.read src+ $ S.unfold FH.readChunksWithBufferOf ((256*1024), src) +-- Copy a source file to a destination file.+--+-- FH.read reads the file in 32KB chunks and converts the chunks into a byte+-- stream. FH.write takes the byte stream as input, converts it into chunks of+-- 32KB and writes those chunks to the destination file.+_cp :: FH.Handle -> FH.Handle -> IO ()+_cp src dst = S.fold (FH.write dst) $ S.unfold FH.read src++-- Chunked version, more efficient than the byte stream version above. Reads+-- the file in 256KB chunks and writes those chunks to stdout. cp :: FH.Handle -> FH.Handle -> IO () cp src dst = S.fold (FH.writeChunks dst)- $ IFH.toChunksWithBufferOf (256*1024) src--- byte stream version--- cp src dst = S.fold (FH.write dst) $ FH.read src+ $ S.unfold FH.readChunksWithBufferOf ((256*1024), src) ord' :: Num a => Char -> a ord' = (fromIntegral . ord) +-- Count lines like wc -l.+--+-- Char stream version. Reads the input as a byte stream, splits it into lines+-- and counts the lines..+_wcl :: FH.Handle -> IO ()+_wcl src = print =<< (S.length+ $ US.lines FL.drain+ $ US.decodeLatin1+ $ S.unfold FH.read src)++-- More efficient chunked version. Reads chunks from the input handles and+-- splits the chunks directly instead of converting them into byte stream+-- first. wcl :: FH.Handle -> IO () wcl src = print =<< (S.length $ AS.splitOn 10- $ IFH.toChunks src)-{---- Char stream version-wcl src = print =<< (S.length- $ flip US.lines FL.drain- $ US.decodeLatin1- $ FH.read src)--}+ $ S.unfold FH.readChunks src) -{-+-- grep -c+--+-- count the occurrences of a pattern in a file. grepc :: String -> FH.Handle -> IO () grepc pat src = print . (subtract 1) =<< (S.length- $ FL.splitOnSeq (A.fromList (map ord' pat)) FL.drain- $ FH.read src)--}+ $ S.splitOnSeq (A.fromList (map ord' pat)) FL.drain+ $ S.unfold FH.read src) +-- Compute the average line length in a file. avgll :: FH.Handle -> IO () avgll src = print =<< (S.fold avg $ S.splitWithSuffix (== ord' '\n') FL.length@@ -57,6 +82,7 @@ where avg = (/) <$> toDouble FL.sum <*> toDouble FL.length toDouble = fmap (fromIntegral :: Int -> Double) +-- histogram of line lengths in a file llhisto :: FH.Handle -> IO () llhisto src = print =<< (S.fold (FL.classify FL.length) $ S.map bucket@@ -73,7 +99,7 @@ rewind >> putStrLn "cat" >> cat src -- Unix cat program rewind >> putStr "wcl " >> wcl src -- Unix wc -l program- -- rewind >> putStr "grepc " >> grepc "aaaa" src -- Unix grep -c program+ rewind >> putStr "grepc " >> grepc "aaaa" src -- Unix grep -c program rewind >> putStr "avgll " >> avgll src -- get average line length rewind >> putStr "llhisto " >> llhisto src -- get line length histogram
examples/ListDir.hs view
@@ -1,8 +1,12 @@-import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))-import Streamly (aheadly, ahead, AheadT)+module Main (main) where++import Data.Bifunctor (bimap) import Data.Function ((&))+import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))+import Streamly (ahead) import qualified Streamly.Prelude as S+import qualified Streamly.Internal.Prelude as S import qualified Streamly.Internal.FileSystem.Dir as Dir -- | List the current directory recursively using concurrent processing@@ -10,22 +14,12 @@ main :: IO () main = do hSetBuffering stdout LineBuffering- S.mapM_ print $ aheadly $ recursePath (Left ".")+ S.mapM_ print $ S.concatMapTreeWith ahead listDir+ (S.yieldM $ return (Left ".")) where - -- XXX Fix bug in enqueueAhead when mixing serial with ahead:- -- (\x -> S.yield dir <> S.concatMapWith ahead recursePath x)- -- :: SerialT IO String- -- or S.cons dir . S.concatMapWith ahead recursePath- recursePath :: Either String String -> AheadT IO String- recursePath (Left dir) =- Dir.toEither dir -- SerialT IO (Either String String)- & S.map (prefixDir dir) -- SerialT IO (Either String String)- & S.consM (return dir)- . S.concatMapWith ahead recursePath -- SerialT IO String- recursePath (Right file) = S.yield file -- SerialT IO String-- prefixDir :: String -> Either String String -> Either String String- prefixDir dir (Right x) = Right $ dir ++ "/" ++ x- prefixDir dir (Left x) = Left $ dir ++ "/" ++ x+ listDir dir =+ Dir.toEither dir -- SerialT IO (Either String String)+ & S.map (bimap prefix prefix) -- SerialT IO (Either String String)+ where prefix x = dir ++ "/" ++ x
src/Streamly.hs view
@@ -52,13 +52,14 @@ -- package. {-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} #if __GLASGOW_HASKELL__ >= 800 {-# OPTIONS_GHC -Wno-orphans #-} #endif -#include "Streamly/Streams/inline.hs"+#include "inline.hs" module Streamly (@@ -128,10 +129,10 @@ -- * Parallel Function Application -- $application- , (|$)- , (|&)- , (|$.)- , (|&.)+ , (IP.|$)+ , (IP.|&)+ , (IP.|$.)+ , (IP.|&.) , mkAsync -- * Merging Streams@@ -224,19 +225,20 @@ import Data.Semigroup (Semigroup(..)) import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))-import Streamly.Streams.Ahead-import Streamly.Streams.Async-import Streamly.Streams.Combinators-import Streamly.Streams.Parallel-import Streamly.Streams.Serial-import Streamly.Streams.StreamK hiding (serial)-import Streamly.Streams.Zip+import Streamly.Internal.Data.Stream.Ahead+import Streamly.Internal.Data.Stream.Async hiding (mkAsync)+import Streamly.Internal.Data.Stream.Combinators+import Streamly.Internal.Data.Stream.Parallel+import Streamly.Internal.Data.Stream.Serial+import Streamly.Internal.Data.Stream.StreamK hiding (serial)+import Streamly.Internal.Data.Stream.Zip import qualified Streamly.Prelude as P import qualified Streamly.Internal.Prelude as IP-import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.Async as Async --- XXX provide good succinct examples of pipelining, merging, splitting ect.+-- XXX provide good succinct examples of pipelining, merging, splitting etc. -- below. -- -- $streams@@ -451,6 +453,19 @@ forEachWith = P.forEachWith -} +-- XXX Deprecate it in 0.8.0+--+-- | Make a stream asynchronous, triggers the computation and returns a stream+-- in the underlying monad representing the output generated by the original+-- computation. The returned action is exhaustible and must be drained once. If+-- not drained fully we may have a thread blocked forever and once exhausted it+-- will always return 'empty'.+--+-- @since 0.2.0+{-# INLINABLE mkAsync #-}+mkAsync :: (IsStream t, MonadAsync m) => t m a -> m (t m a)+mkAsync = return . Async.mkAsync+ ------------------------------------------------------------------------------ -- Documentation ------------------------------------------------------------------------------@@ -511,39 +526,43 @@ -- $async ----- When a stream consumer demands an element from an asynchronous stream,--- constructed as @a \`consM` b \`consM` ... nil@, the action @a@ along with--- multiple following at the head of the stream sequence are executed--- concurrently and the output of the one that completes first is supplied to--- the consumer. As more actions complete, their results are buffered in the--- order of completion. When the next element is demanded it may be served--- from the buffer and we may initiate execution of more actions in the--- sequence to keep the buffer adequately filled. Thus, the actions are--- executed concurrently and their results are consumed in the order of--- completion. `consM` can be used to fold an infinite lazy container of--- effects, as the number of concurrent executions is limited.+-- /Scheduling and execution:/ In an asynchronous stream @a \`consM` b \`consM`+-- c ...@, the actions @a@, @b@, and @c@ are executed concurrently with the+-- consumer of the stream. The actions are /scheduled/ for execution in the+-- same order as they are specified in the stream. Multiple scheduled actions+-- may be /executed/ concurrently in parallel threads of execution. The+-- actions may be executed out of order and they may complete at arbitrary+-- times. Therefore, the /effects/ of the actions may be observed out of+-- order. ----- Similar to 'consM', the monadic stream generation (e.g. replicateM) and--- transformation operations (e.g. mapM) on asynchronous streams can execute--- multiple effects concurrently in an asynchronous manner.+-- /Buffering:/ The /results/ from multiple threads of execution are queued in+-- a buffer as soon as they become available. The consumer of the stream is+-- served from this buffer. Therefore, the consumer may observe the results to+-- be out of order. In other words, an asynchronous stream is an unordered+-- stream i.e. order does not matter. ----- How many effects can be executed concurrently and how many results can be--- buffered are controlled by 'maxThreads' and 'maxBuffer' combinators--- respectively. The actual number of concurrent threads is adjusted according--- to the rate at which the consumer is consuming the stream. It may even--- execute actions serially in a single thread if that is enough to match the--- consumer's speed.+-- /Concurrency control:/ Threads are suspended if the `maxBuffer` limit is+-- reached, and resumed when the consumer makes space in the buffer. The+-- maximum number of concurrent threads depends on `maxThreads`. Number of+-- threads is increased or decreased based on the speed of the consumer. ----- Asynchronous streams do not enforce any spatial order on the side effects or--- on the results of the actions. However there is a partial ordering as the--- actions to be executed are picked from the head of stream. The results are--- presented to the consumer in the completion time order. Therefore, the--- semigroup operation for asynchronous streams is commutative i.e. the stream--- is considered unordered.+-- /Generation operations:/ Concurrent stream generation operations e.g.+-- 'Streamly.Prelude.replicateM' when used in async style schedule and execute+-- the stream generating actions in the manner described above. The generation+-- actions run concurrently, effects and results of the actions as observed by+-- the consumer of the stream may be out of order. ----- There are two asynchronous stream types 'AsyncT' and 'WAsyncT'. The stream--- evaluation of both the variants works in the same way as described above,--- they differ only in the 'Semigroup' and 'Monad' implementaitons.+-- /Transformation operations:/ Concurrent stream transformation operations+-- e.g. 'Streamly.Prelude.mapM', when used in async style, schedule and+-- execute transformation actions in the manner described above. Transformation+-- actions run concurrently, effects and results of the actions may be+-- observed by the consumer out of order.+--+-- /Variants:/ There are two asynchronous stream types 'AsyncT' and 'WAsyncT'.+-- They are identical with respect to single stream evaluation behavior. Their+-- behaviors differ in how they combine multiple streams using 'Semigroup' or+-- 'Monad' composition. Since the order of elements does not matter in+-- asynchronous streams the 'Semigroup' operation is effectively commutative. -- $zipping --
− src/Streamly/Benchmark/FileIO/Array.hs
@@ -1,242 +0,0 @@--- |--- Module : Streamly.Benchmark.FileIO.Array--- Copyright : (c) 2019 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--{-# LANGUAGE CPP #-}--#ifdef __HADDOCK_VERSION__-#undef INSPECTION-#endif--#ifdef INSPECTION-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}-#endif--module Streamly.Benchmark.FileIO.Array- (- last- , countBytes- , countLines- , countWords- , sumBytes- , cat- , catOnException- , catBracket- , catBracketStream- , copy- , linesUnlinesCopy- , wordsUnwordsCopy- , decodeUtf8Lenient- , copyCodecUtf8Lenient- )-where--import Data.Functor.Identity (runIdentity)-import Data.Word (Word8)-import System.IO (Handle, hClose)-import Prelude hiding (last)--import qualified Streamly.FileSystem.Handle as FH-import qualified Streamly.Memory.Array as A-import qualified Streamly.Prelude as S-import qualified Streamly.Data.Unicode.Stream as SS-import qualified Streamly.Internal.Data.Unicode.Stream as IUS--import qualified Streamly.Internal.FileSystem.Handle as IFH-import qualified Streamly.Internal.Memory.Array as IA-import qualified Streamly.Internal.Memory.ArrayStream as AS-import qualified Streamly.Internal.Data.Unfold as IUF--#ifdef INSPECTION-import Foreign.Storable (Storable)-import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))-import Test.Inspection-#endif---- | Get the last byte from a file bytestream.-{-# INLINE last #-}-last :: Handle -> IO (Maybe Word8)-last inh = do- let s = IFH.toChunks inh- larr <- S.last s- return $ case larr of- Nothing -> Nothing- Just arr -> IA.readIndex arr (A.length arr - 1)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'last-inspect $ 'last `hasNoType` ''Step-#endif---- | Count the number of bytes in a file.-{-# INLINE countBytes #-}-countBytes :: Handle -> IO Int-countBytes inh =- let s = IFH.toChunks inh- in S.sum (S.map A.length s)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countBytes-inspect $ 'countBytes `hasNoType` ''Step-#endif---- | Count the number of lines in a file.-{-# INLINE countLines #-}-countLines :: Handle -> IO Int-countLines = S.length . AS.splitOnSuffix 10 . IFH.toChunks--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countLines-inspect $ 'countLines `hasNoType` ''Step-#endif---- XXX use a word splitting combinator instead of splitOn and test it.--- | Count the number of lines in a file.-{-# INLINE countWords #-}-countWords :: Handle -> IO Int-countWords = S.length . AS.splitOn 32 . IFH.toChunks--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countWords-inspect $ 'countWords `hasNoType` ''Step-#endif---- | Sum the bytes in a file.-{-# INLINE sumBytes #-}-sumBytes :: Handle -> IO Word8-sumBytes inh = do- let foldlArr' f z = runIdentity . S.foldl' f z . IA.toStream- let s = IFH.toChunks inh- S.foldl' (\acc arr -> acc + foldlArr' (+) 0 arr) 0 s--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'sumBytes-inspect $ 'sumBytes `hasNoType` ''Step-#endif---- | Send the file contents to /dev/null-{-# INLINE cat #-}-cat :: Handle -> Handle -> IO ()-cat devNull inh =- S.fold (IFH.writeChunks devNull) $ IFH.toChunksWithBufferOf (256*1024) inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'cat-inspect $ 'cat `hasNoType` ''Step-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catBracket #-}-catBracket :: Handle -> Handle -> IO ()-catBracket devNull inh =- let readEx = IUF.bracket return (\_ -> hClose inh)- (IUF.supplyFirst FH.readChunksWithBufferOf (256*1024))- in IUF.fold readEx (IFH.writeChunks devNull) inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catBracket--- inspect $ 'catBracket `hasNoType` ''Step-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catBracketStream #-}-catBracketStream :: Handle -> Handle -> IO ()-catBracketStream devNull inh =- let readEx = S.bracket (return ()) (\_ -> hClose inh)- (\_ -> IFH.toChunksWithBufferOf (256*1024) inh)- in S.fold (IFH.writeChunks devNull) $ readEx--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catBracketStream--- inspect $ 'catBracketStream `hasNoType` ''Step-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catOnException #-}-catOnException :: Handle -> Handle -> IO ()-catOnException devNull inh =- let readEx = IUF.onException (\_ -> hClose inh)- (IUF.supplyFirst FH.readChunksWithBufferOf (256*1024))- in IUF.fold readEx (IFH.writeChunks devNull) inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catOnException--- inspect $ 'catOnException `hasNoType` ''Step-#endif---- | Copy file-{-# INLINE copy #-}-copy :: Handle -> Handle -> IO ()-copy inh outh =- let s = IFH.toChunks inh- in S.fold (IFH.writeChunks outh) s--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copy-inspect $ 'copy `hasNoType` ''Step-#endif---- | Lines and unlines-{-# INLINE linesUnlinesCopy #-}-linesUnlinesCopy :: Handle -> Handle -> IO ()-linesUnlinesCopy inh outh =- S.fold (IFH.writeWithBufferOf (1024*1024) outh)- $ AS.interposeSuffix 10- $ AS.splitOnSuffix 10- $ IFH.toChunksWithBufferOf (1024*1024) inh--#ifdef INSPECTION-inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]--- inspect $ 'linesUnlinesCopy `hasNoType` ''Step-#endif---- | Words and unwords-{-# INLINE wordsUnwordsCopy #-}-wordsUnwordsCopy :: Handle -> Handle -> IO ()-wordsUnwordsCopy inh outh =- S.fold (IFH.writeWithBufferOf (1024*1024) outh)- $ AS.interpose 32- -- XXX this is not correct word splitting combinator- $ AS.splitOn 32- $ IFH.toChunksWithBufferOf (1024*1024) inh--#ifdef INSPECTION-inspect $ hasNoTypeClassesExcept 'wordsUnwordsCopy [''Storable]--- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step-#endif--{-# INLINE decodeUtf8Lenient #-}-decodeUtf8Lenient :: Handle -> IO ()-decodeUtf8Lenient inh =- S.drain- $ IUS.decodeUtf8ArraysLenient- $ IFH.toChunksWithBufferOf (1024*1024) inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'decodeUtf8Lenient--- inspect $ 'decodeUtf8Lenient `hasNoType` ''Step--- inspect $ 'decodeUtf8Lenient `hasNoType` ''AT.FlattenState--- inspect $ 'decodeUtf8Lenient `hasNoType` ''D.ConcatMapUState-#endif---- | Copy file-{-# INLINE copyCodecUtf8Lenient #-}-copyCodecUtf8Lenient :: Handle -> Handle -> IO ()-copyCodecUtf8Lenient inh outh =- S.fold (FH.write outh)- $ SS.encodeUtf8- $ IUS.decodeUtf8ArraysLenient- $ IFH.toChunksWithBufferOf (1024*1024) inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState-#endif
− src/Streamly/Benchmark/FileIO/Stream.hs
@@ -1,617 +0,0 @@--- |--- Module : Streamly.Benchmark.FileIO.Stream--- Copyright : (c) 2019 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--{-# LANGUAGE CPP #-}-{-# LANGUAGE ScopedTypeVariables #-}--#ifdef __HADDOCK_VERSION__-#undef INSPECTION-#endif--#ifdef INSPECTION-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}-#endif--module Streamly.Benchmark.FileIO.Stream- (- -- * FileIO- last- , countBytes- , countLines- , countLinesU- , countWords- , sumBytes- , cat- , catStreamWrite- , catBracket- , catBracketStream- , catOnException- , catOnExceptionStream- , catHandle- , catHandleStream- , catFinally- , catFinallyStream- , copy- , linesUnlinesCopy- , linesUnlinesArrayWord8Copy- , linesUnlinesArrayCharCopy- -- , linesUnlinesArrayUtf8Copy- , wordsUnwordsCopyWord8- , wordsUnwordsCopy- , wordsUnwordsCharArrayCopy- , readWord8- , decodeLatin1- , copyCodecChar8- , copyCodecUtf8- , decodeUtf8Lax- , copyCodecUtf8Lenient- , chunksOf- , chunksOfD- , splitOn- , splitOnSuffix- , wordsBy- , splitOnSeq- , splitOnSeqUtf8- , splitOnSuffixSeq- )-where--import Control.Exception (SomeException)-import Data.Char (ord, chr)-import Data.Word (Word8)-import System.IO (Handle, hClose)-import Prelude hiding (last, length)--import qualified Streamly.FileSystem.Handle as FH-import qualified Streamly.Internal.FileSystem.Handle as IFH-import qualified Streamly.Memory.Array as A--- import qualified Streamly.Internal.Memory.Array as IA-import qualified Streamly.Internal.Memory.Array.Types as AT-import qualified Streamly.Prelude as S-import qualified Streamly.Data.Fold as FL--- import qualified Streamly.Internal.Data.Fold as IFL-import qualified Streamly.Data.Unicode.Stream as SS-import qualified Streamly.Internal.Data.Unicode.Stream as IUS-import qualified Streamly.Internal.Memory.Unicode.Array as IUA-import qualified Streamly.Internal.Data.Unfold as IUF-import qualified Streamly.Internal.Prelude as IP-import qualified Streamly.Streams.StreamD as D--#ifdef INSPECTION-import Foreign.Storable (Storable)-import Streamly.Internal.Data.Stream.StreamD.Type (Step(..), GroupState)-import Test.Inspection-#endif---- | Get the last byte from a file bytestream.-{-# INLINE last #-}-last :: Handle -> IO (Maybe Word8)-last = S.last . S.unfold FH.read--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'last-inspect $ 'last `hasNoType` ''Step-inspect $ 'last `hasNoType` ''AT.FlattenState-inspect $ 'last `hasNoType` ''D.ConcatMapUState-#endif---- assert that flattenArrays constructors are not present--- | Count the number of bytes in a file.-{-# INLINE countBytes #-}-countBytes :: Handle -> IO Int-countBytes = S.length . S.unfold FH.read--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countBytes-inspect $ 'countBytes `hasNoType` ''Step-inspect $ 'countBytes `hasNoType` ''AT.FlattenState-inspect $ 'countBytes `hasNoType` ''D.ConcatMapUState-#endif---- | Count the number of lines in a file.-{-# INLINE countLines #-}-countLines :: Handle -> IO Int-countLines =- S.length- . IUS.lines FL.drain- . SS.decodeLatin1- . S.unfold FH.read--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countLines-inspect $ 'countLines `hasNoType` ''Step-inspect $ 'countLines `hasNoType` ''AT.FlattenState-inspect $ 'countLines `hasNoType` ''D.ConcatMapUState-#endif---- | Count the number of words in a file.-{-# INLINE countWords #-}-countWords :: Handle -> IO Int-countWords =- S.length- . IUS.words FL.drain- . SS.decodeLatin1- . S.unfold FH.read--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countWords--- inspect $ 'countWords `hasNoType` ''Step--- inspect $ 'countWords `hasNoType` ''D.ConcatMapUState-#endif---- | Count the number of lines in a file.-{-# INLINE countLinesU #-}-countLinesU :: Handle -> IO Int-countLinesU inh =- S.length- $ IUS.lines FL.drain- $ SS.decodeLatin1- $ S.concatUnfold A.read (IFH.toChunks inh)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'countLinesU-inspect $ 'countLinesU `hasNoType` ''Step-inspect $ 'countLinesU `hasNoType` ''D.ConcatMapUState-#endif---- | Sum the bytes in a file.-{-# INLINE sumBytes #-}-sumBytes :: Handle -> IO Word8-sumBytes = S.sum . S.unfold FH.read--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'sumBytes-inspect $ 'sumBytes `hasNoType` ''Step-inspect $ 'sumBytes `hasNoType` ''AT.FlattenState-inspect $ 'sumBytes `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null-{-# INLINE cat #-}-cat :: Handle -> Handle -> IO ()-cat devNull inh = S.fold (FH.write devNull) $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'cat-inspect $ 'cat `hasNoType` ''Step-inspect $ 'cat `hasNoType` ''AT.FlattenState-inspect $ 'cat `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null-{-# INLINE catStreamWrite #-}-catStreamWrite :: Handle -> Handle -> IO ()-catStreamWrite devNull inh = IFH.fromBytes devNull $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catStreamWrite-inspect $ 'catStreamWrite `hasNoType` ''Step-inspect $ 'catStreamWrite `hasNoType` ''AT.FlattenState-inspect $ 'catStreamWrite `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catBracket #-}-catBracket :: Handle -> Handle -> IO ()-catBracket devNull inh =- let readEx = IUF.bracket return (\_ -> hClose inh) FH.read- in S.fold (FH.write devNull) $ S.unfold readEx inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catBracket--- inspect $ 'catBracket `hasNoType` ''Step--- inspect $ 'catBracket `hasNoType` ''AT.FlattenState--- inspect $ 'catBracket `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catBracketStream #-}-catBracketStream :: Handle -> Handle -> IO ()-catBracketStream devNull inh =- let readEx = S.bracket (return ()) (\_ -> hClose inh)- (\_ -> IFH.toBytes inh)- in IFH.fromBytes devNull $ readEx--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catBracketStream--- inspect $ 'catBracketStream `hasNoType` ''Step-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catOnException #-}-catOnException :: Handle -> Handle -> IO ()-catOnException devNull inh =- let readEx = IUF.onException (\_ -> hClose inh) FH.read- in S.fold (FH.write devNull) $ S.unfold readEx inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catOnException--- inspect $ 'catOnException `hasNoType` ''Step--- inspect $ 'catOnException `hasNoType` ''AT.FlattenState--- inspect $ 'catOnException `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catOnExceptionStream #-}-catOnExceptionStream :: Handle -> Handle -> IO ()-catOnExceptionStream devNull inh =- let readEx = S.onException (hClose inh) (S.unfold FH.read inh)- in S.fold (FH.write devNull) $ readEx---- | Send the file contents to /dev/null with exception handling-{-# INLINE catFinally #-}-catFinally :: Handle -> Handle -> IO ()-catFinally devNull inh =- let readEx = IUF.finally (\_ -> hClose inh) FH.read- in S.fold (FH.write devNull) $ S.unfold readEx inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catFinally--- inspect $ 'catFinally `hasNoType` ''Step--- inspect $ 'catFinally `hasNoType` ''AT.FlattenState--- inspect $ 'catFinally `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catFinallyStream #-}-catFinallyStream :: Handle -> Handle -> IO ()-catFinallyStream devNull inh =- let readEx = S.finally (hClose inh) (S.unfold FH.read inh)- in S.fold (FH.write devNull) readEx---- | Send the file contents to /dev/null with exception handling-{-# INLINE catHandle #-}-catHandle :: Handle -> Handle -> IO ()-catHandle devNull inh =- let handler (_e :: SomeException) = hClose inh >> return 10- readEx = IUF.handle (IUF.singleton handler) FH.read- in S.fold (FH.write devNull) $ S.unfold readEx inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'catHandle--- inspect $ 'catHandle `hasNoType` ''Step--- inspect $ 'catHandle `hasNoType` ''AT.FlattenState--- inspect $ 'catHandle `hasNoType` ''D.ConcatMapUState-#endif---- | Send the file contents to /dev/null with exception handling-{-# INLINE catHandleStream #-}-catHandleStream :: Handle -> Handle -> IO ()-catHandleStream devNull inh =- let handler (_e :: SomeException) = S.yieldM (hClose inh >> return 10)- readEx = S.handle handler (S.unfold FH.read inh)- in S.fold (FH.write devNull) $ readEx---- | Copy file-{-# INLINE copy #-}-copy :: Handle -> Handle -> IO ()-copy inh outh = S.fold (FH.write outh) (S.unfold FH.read inh)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copy-inspect $ 'copy `hasNoType` ''Step-inspect $ 'copy `hasNoType` ''AT.FlattenState-inspect $ 'copy `hasNoType` ''D.ConcatMapUState-#endif--{-# INLINE readWord8 #-}-readWord8 :: Handle -> IO ()-readWord8 inh = S.drain $ S.unfold FH.read inh--{-# INLINE decodeLatin1 #-}-decodeLatin1 :: Handle -> IO ()-decodeLatin1 inh =- S.drain- $ SS.decodeLatin1- $ S.unfold FH.read inh---- | Copy file-{-# INLINE copyCodecChar8 #-}-copyCodecChar8 :: Handle -> Handle -> IO ()-copyCodecChar8 inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ SS.decodeLatin1- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copyCodecChar8-inspect $ 'copyCodecChar8 `hasNoType` ''Step-inspect $ 'copyCodecChar8 `hasNoType` ''AT.FlattenState-inspect $ 'copyCodecChar8 `hasNoType` ''D.ConcatMapUState-#endif--{-# INLINE decodeUtf8Lax #-}-decodeUtf8Lax :: Handle -> IO ()-decodeUtf8Lax inh =- S.drain- $ SS.decodeUtf8Lax- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'decodeUtf8Lax--- inspect $ 'decodeUtf8Lax `hasNoType` ''Step--- inspect $ 'decodeUtf8Lax `hasNoType` ''AT.FlattenState--- inspect $ 'decodeUtf8Lax `hasNoType` ''D.ConcatMapUState-#endif---- | Copy file-{-# INLINE copyCodecUtf8 #-}-copyCodecUtf8 :: Handle -> Handle -> IO ()-copyCodecUtf8 inh outh =- S.fold (FH.write outh)- $ SS.encodeUtf8- $ SS.decodeUtf8- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copyCodecUtf8--- inspect $ 'copyCodecUtf8 `hasNoType` ''Step--- inspect $ 'copyCodecUtf8 `hasNoType` ''AT.FlattenState--- inspect $ 'copyCodecUtf8 `hasNoType` ''D.ConcatMapUState-#endif---- | Copy file-{-# INLINE copyCodecUtf8Lenient #-}-copyCodecUtf8Lenient :: Handle -> Handle -> IO ()-copyCodecUtf8Lenient inh outh =- S.fold (FH.write outh)- $ SS.encodeUtf8- $ SS.decodeUtf8Lax- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState--- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState-#endif---- | Slice in chunks of size n and get the count of chunks.-{-# INLINE chunksOf #-}-chunksOf :: Int -> Handle -> IO Int-chunksOf n inh =- -- writeNUnsafe gives 2.5x boost here over writeN.- S.length $ S.chunksOf n (AT.writeNUnsafe n) (S.unfold FH.read inh)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'chunksOf-inspect $ 'chunksOf `hasNoType` ''Step-inspect $ 'chunksOf `hasNoType` ''AT.FlattenState-inspect $ 'chunksOf `hasNoType` ''D.ConcatMapUState-inspect $ 'chunksOf `hasNoType` ''GroupState-#endif---- This is to make sure that the concatMap in FH.read, groupsOf and foldlM'--- together can fuse.------ | Slice in chunks of size n and get the count of chunks.-{-# INLINE chunksOfD #-}-chunksOfD :: Int -> Handle -> IO Int-chunksOfD n inh =- D.foldlM' (\i _ -> return $ i + 1) 0- $ D.groupsOf n (AT.writeNUnsafe n)- $ D.fromStreamK (S.unfold FH.read inh)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'chunksOf-inspect $ 'chunksOf `hasNoType` ''Step-inspect $ 'chunksOfD `hasNoType` ''GroupState-inspect $ 'chunksOfD `hasNoType` ''AT.FlattenState-inspect $ 'chunksOfD `hasNoType` ''D.ConcatMapUState-#endif--{-# INLINE linesUnlinesCopy #-}-linesUnlinesCopy :: Handle -> Handle -> IO ()-linesUnlinesCopy inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ IUS.unlines IUF.fromList- $ S.splitOnSuffix (== '\n') FL.toList- $ SS.decodeLatin1- $ S.unfold FH.read inh--{-# INLINE linesUnlinesArrayWord8Copy #-}-linesUnlinesArrayWord8Copy :: Handle -> Handle -> IO ()-linesUnlinesArrayWord8Copy inh outh =- S.fold (FH.write outh)- $ IP.interposeSuffix 10 A.read- $ S.splitOnSuffix (== 10) A.write- $ S.unfold FH.read inh---- XXX splitSuffixOn requires -funfolding-use-threshold=150 for better fusion--- | Lines and unlines-{-# INLINE linesUnlinesArrayCharCopy #-}-linesUnlinesArrayCharCopy :: Handle -> Handle -> IO ()-linesUnlinesArrayCharCopy inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ IUA.unlines- $ IUA.lines- $ SS.decodeLatin1- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClassesExcept 'linesUnlinesArrayCharCopy [''Storable]--- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''Step--- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''AT.FlattenState--- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''D.ConcatMapUState-#endif---- XXX to write this we need to be able to map decodeUtf8 on the A.read fold.--- For that we have to write decodeUtf8 as a Pipe.-{--{-# INLINE linesUnlinesArrayUtf8Copy #-}-linesUnlinesArrayUtf8Copy :: Handle -> Handle -> IO ()-linesUnlinesArrayUtf8Copy inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ IP.intercalate (A.fromList [10]) (pipe SS.decodeUtf8P A.read)- $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8 A.write)- $ SS.decodeLatin1- $ S.unfold FH.read inh--}--foreign import ccall unsafe "u_iswspace"- iswspace :: Int -> Int---- Code copied from base/Data.Char to INLINE it-{-# INLINE isSpace #-}-isSpace :: Char -> Bool--- isSpace includes non-breaking space--- The magic 0x377 isn't really that magical. As of 2014, all the codepoints--- at or below 0x377 have been assigned, so we shouldn't have to worry about--- any new spaces appearing below there. It would probably be best to--- use branchless ||, but currently the eqLit transformation will undo that,--- so we'll do it like this until there's a way around that.-isSpace c- | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0- | otherwise = iswspace (ord c) /= 0- where- uc = fromIntegral (ord c) :: Word--{-# INLINE isSp #-}-isSp :: Word8 -> Bool-isSp = isSpace . chr . fromIntegral---- | Word, unwords and copy-{-# INLINE wordsUnwordsCopyWord8 #-}-wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()-wordsUnwordsCopyWord8 inh outh =- S.fold (FH.write outh)- $ IP.interposeSuffix 32 IUF.fromList- $ S.wordsBy isSp FL.toList- $ S.unfold FH.read inh--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8--- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step--- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''D.ConcatMapUState-#endif---- | Word, unwords and copy-{-# INLINE wordsUnwordsCopy #-}-wordsUnwordsCopy :: Handle -> Handle -> IO ()-wordsUnwordsCopy inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ IUS.unwords IUF.fromList- -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn- -- with -funfolding-use-threshold=300. With wordsBy it does not fuse- -- even with high limits for inlining and spec-constr ghc options. With- -- -funfolding-use-threshold=400 it performs pretty well and there- -- is no evidence in the core that a join point involving Step- -- constructors is not getting inlined. Not being able to fuse at all in- -- this case could be an unknown issue, need more investigation.- $ S.wordsBy isSpace FL.toList- -- -- $ S.splitOn isSpace FL.toList- $ SS.decodeLatin1- $ S.unfold FH.read inh--#ifdef INSPECTION--- inspect $ hasNoTypeClasses 'wordsUnwordsCopy--- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step--- inspect $ 'wordsUnwordsCopy `hasNoType` ''AT.FlattenState--- inspect $ 'wordsUnwordsCopy `hasNoType` ''D.ConcatMapUState-#endif--{-# INLINE wordsUnwordsCharArrayCopy #-}-wordsUnwordsCharArrayCopy :: Handle -> Handle -> IO ()-wordsUnwordsCharArrayCopy inh outh =- S.fold (FH.write outh)- $ SS.encodeLatin1- $ IUA.unwords- $ IUA.words- $ SS.decodeLatin1- $ S.unfold FH.read inh--lf :: Word8-lf = fromIntegral (ord '\n')--toarr :: String -> A.Array Word8-toarr = A.fromList . map (fromIntegral . ord)---- | Split on line feed.-{-# INLINE splitOn #-}-splitOn :: Handle -> IO Int-splitOn inh =- (S.length $ S.splitOn (== lf) FL.drain- $ S.unfold FH.read inh) -- >>= print--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'splitOn-inspect $ 'splitOn `hasNoType` ''Step-inspect $ 'splitOn `hasNoType` ''AT.FlattenState-inspect $ 'splitOn `hasNoType` ''D.ConcatMapUState-#endif---- | Split suffix on line feed.-{-# INLINE splitOnSuffix #-}-splitOnSuffix :: Handle -> IO Int-splitOnSuffix inh =- (S.length $ S.splitOnSuffix (== lf) FL.drain- $ S.unfold FH.read inh) -- >>= print--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'splitOnSuffix-inspect $ 'splitOnSuffix `hasNoType` ''Step-inspect $ 'splitOnSuffix `hasNoType` ''AT.FlattenState-inspect $ 'splitOnSuffix `hasNoType` ''D.ConcatMapUState-#endif---- | Words by space-{-# INLINE wordsBy #-}-wordsBy :: Handle -> IO Int-wordsBy inh =- (S.length $ S.wordsBy isSp FL.drain- $ S.unfold FH.read inh) -- >>= print--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'wordsBy-inspect $ 'wordsBy `hasNoType` ''Step-inspect $ 'wordsBy `hasNoType` ''AT.FlattenState-inspect $ 'wordsBy `hasNoType` ''D.ConcatMapUState-#endif---- | Split on a word8 sequence.-{-# INLINE splitOnSeq #-}-splitOnSeq :: String -> Handle -> IO Int-splitOnSeq str inh =- (S.length $ IP.splitOnSeq (toarr str) FL.drain- $ S.unfold FH.read inh) -- >>= print--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'splitOnSeq--- inspect $ 'splitOnSeq `hasNoType` ''Step--- inspect $ 'splitOnSeq `hasNoType` ''AT.FlattenState--- inspect $ 'splitOnSeq `hasNoType` ''D.ConcatMapUState-#endif---- | Split on a character sequence.-{-# INLINE splitOnSeqUtf8 #-}-splitOnSeqUtf8 :: String -> Handle -> IO Int-splitOnSeqUtf8 str inh =- (S.length $ IP.splitOnSeq (A.fromList str) FL.drain- $ IUS.decodeUtf8ArraysLenient- $ IFH.toChunks inh) -- >>= print---- | Split on suffix sequence.-{-# INLINE splitOnSuffixSeq #-}-splitOnSuffixSeq :: String -> Handle -> IO Int-splitOnSuffixSeq str inh =- (S.length $ IP.splitOnSuffixSeq (toarr str) FL.drain- $ S.unfold FH.read inh) -- >>= print--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'splitOnSuffixSeq--- inspect $ 'splitOnSuffixSeq `hasNoType` ''Step--- inspect $ 'splitOnSuffixSeq `hasNoType` ''AT.FlattenState--- inspect $ 'splitOnSuffixSeq `hasNoType` ''D.ConcatMapUState-#endif
− src/Streamly/Benchmark/Prelude.hs
@@ -1,912 +0,0 @@--- |--- Module : Streamly.Benchmark.Prelude--- Copyright : (c) 2018 Harendra Kumar------ License : MIT--- Maintainer : streamly@composewell.com--{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}--#ifdef __HADDOCK_VERSION__-#undef INSPECTION-#endif--#ifdef INSPECTION-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}-#endif--module Streamly.Benchmark.Prelude where--import Control.DeepSeq (NFData)-import Control.Monad (when)-import Control.Monad.IO.Class (MonadIO)-import Control.Monad.State.Strict (StateT, get, put)-import Data.Functor.Identity (Identity, runIdentity)-import Data.Maybe (fromJust)-import GHC.Generics (Generic)-import Prelude- (Monad, Int, (+), ($), (.), return, fmap, even, (>), (<=), (==), (>=),- subtract, undefined, Maybe(..), odd, Bool, not, (>>=), mapM_, curry,- maxBound, div, IO, compare, Double, fromIntegral, Integer, (<$>),- (<*>), flip, (**), (/))-import qualified Prelude as P-import qualified Data.Foldable as F-import qualified GHC.Exts as GHC--#ifdef INSPECTION-import Test.Inspection--import qualified Streamly.Streams.StreamD as D-#endif--import qualified Streamly as S hiding (runStream)-import qualified Streamly.Prelude as S-import qualified Streamly.Internal.Prelude as Internal-import qualified Streamly.Internal.Data.Unfold as UF-import qualified Streamly.Internal.Data.Pipe as Pipe--value, maxValue, value2 :: Int-#ifdef LINEAR_ASYNC-value = 10000-#else-value = 100000-#endif-maxValue = value + 1-value2 = P.round (P.fromIntegral value**(1/2::P.Double)) -- double nested loop------------------------------------------------------------------------------------ Benchmark ops-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Stream generation and elimination----------------------------------------------------------------------------------type Stream m a = S.SerialT m a--{-# INLINE source #-}-source :: (S.MonadAsync m, S.IsStream t) => Int -> t m Int-source n = sourceUnfoldrM n--{-# INLINE sourceIntFromTo #-}-sourceIntFromTo :: (Monad m, S.IsStream t) => Int -> t m Int-sourceIntFromTo n = S.enumerateFromTo n (n + value)--{-# INLINE sourceIntFromThenTo #-}-sourceIntFromThenTo :: (Monad m, S.IsStream t) => Int -> t m Int-sourceIntFromThenTo n = S.enumerateFromThenTo n (n + 1) (n + value)--{-# INLINE sourceFracFromTo #-}-sourceFracFromTo :: (Monad m, S.IsStream t) => Int -> t m Double-sourceFracFromTo n =- S.enumerateFromTo (fromIntegral n) (fromIntegral (n + value))--{-# INLINE sourceFracFromThenTo #-}-sourceFracFromThenTo :: (Monad m, S.IsStream t) => Int -> t m Double-sourceFracFromThenTo n = S.enumerateFromThenTo (fromIntegral n)- (fromIntegral n + 1.0001) (fromIntegral (n + value))--{-# INLINE sourceIntegerFromStep #-}-sourceIntegerFromStep :: (Monad m, S.IsStream t) => Int -> t m Integer-sourceIntegerFromStep n =- S.take value $ S.enumerateFromThen (fromIntegral n) (fromIntegral n + 1)--{-# INLINE sourceFromList #-}-sourceFromList :: (Monad m, S.IsStream t) => Int -> t m Int-sourceFromList n = S.fromList [n..n+value]--{-# INLINE sourceFromListM #-}-sourceFromListM :: (S.MonadAsync m, S.IsStream t) => Int -> t m Int-sourceFromListM n = S.fromListM (Prelude.fmap return [n..n+value])--{-# INLINE sourceFromIndices #-}-sourceFromIndices :: (Monad m, S.IsStream t) => Int -> t m Int-sourceFromIndices n = S.take value $ S.fromIndices (+ n)--{-# INLINE sourceFromIndicesM #-}-sourceFromIndicesM :: (S.MonadAsync m, S.IsStream t) => Int -> t m Int-sourceFromIndicesM n = S.take value $ S.fromIndicesM (Prelude.fmap return (+ n))--{-# INLINE sourceFromFoldable #-}-sourceFromFoldable :: S.IsStream t => Int -> t m Int-sourceFromFoldable n = S.fromFoldable [n..n+value]--{-# INLINE sourceFromFoldableM #-}-sourceFromFoldableM :: (S.IsStream t, S.MonadAsync m) => Int -> t m Int-sourceFromFoldableM n = S.fromFoldableM (Prelude.fmap return [n..n+value])--{-# INLINE sourceFoldMapWith #-}-sourceFoldMapWith :: (S.IsStream t, S.Semigroup (t m Int))- => Int -> t m Int-sourceFoldMapWith n = S.foldMapWith (S.<>) S.yield [n..n+value]--{-# INLINE sourceFoldMapWithM #-}-sourceFoldMapWithM :: (S.IsStream t, Monad m, S.Semigroup (t m Int))- => Int -> t m Int-sourceFoldMapWithM n = S.foldMapWith (S.<>) (S.yieldM . return) [n..n+value]--{-# INLINE sourceFoldMapM #-}-sourceFoldMapM :: (S.IsStream t, Monad m, P.Monoid (t m Int))- => Int -> t m Int-sourceFoldMapM n = F.foldMap (S.yieldM . return) [n..n+value]--{-# INLINE sourceConcatMapId #-}-sourceConcatMapId :: (S.IsStream t, Monad m)- => Int -> t m Int-sourceConcatMapId n =- S.concatMap P.id $ S.fromFoldable $ P.map (S.yieldM . return) [n..n+value]--{-# INLINE sourceUnfoldr #-}-sourceUnfoldr :: (Monad m, S.IsStream t) => Int -> t m Int-sourceUnfoldr n = S.unfoldr step n- where- step cnt =- if cnt > n + value- then Nothing- else Just (cnt, cnt + 1)--{-# INLINE sourceUnfoldrN #-}-sourceUnfoldrN :: (Monad m, S.IsStream t) => Int -> Int -> t m Int-sourceUnfoldrN upto start = S.unfoldr step start- where- step cnt =- if cnt > start + upto- then Nothing- else Just (cnt, cnt + 1)--{-# INLINE sourceUnfoldrM #-}-sourceUnfoldrM :: (S.IsStream t, S.MonadAsync m) => Int -> t m Int-sourceUnfoldrM n = S.unfoldrM step n- where- step cnt =- if cnt > n + value- then return Nothing- else return (Just (cnt, cnt + 1))--{-# INLINE sourceUnfoldrState #-}-sourceUnfoldrState :: (S.IsStream t, S.MonadAsync m)- => Int -> t (StateT Int m) Int-sourceUnfoldrState n = S.unfoldrM step n- where- step cnt =- if cnt > n + value- then return Nothing- else do- s <- get- put (s + 1)- return (Just (s, cnt + 1))--{-# INLINE sourceUnfoldrMN #-}-sourceUnfoldrMN :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int-sourceUnfoldrMN upto start = S.unfoldrM step start- where- step cnt =- if cnt > start + upto- then return Nothing- else return (Just (cnt, cnt + 1))--{-# INLINE sourceUnfoldrMAction #-}-sourceUnfoldrMAction :: (S.IsStream t, S.MonadAsync m) => Int -> t m (m Int)-sourceUnfoldrMAction n = S.serially $ S.unfoldrM step n- where- step cnt =- if cnt > n + value- then return Nothing- else return (Just (return cnt, cnt + 1))------------------------------------------------------------------------------------ Pure stream generation----------------------------------------------------------------------------------{-# INLINE sourceIsList #-}-sourceIsList :: Int -> S.SerialT Identity Int-sourceIsList n = GHC.fromList [n..n+value]--{-# INLINE sourceIsString #-}-sourceIsString :: Int -> S.SerialT Identity P.Char-sourceIsString n = GHC.fromString (P.replicate (n + value) 'a')------------------------------------------------------------------------------------ Elimination----------------------------------------------------------------------------------{-# INLINE runStream #-}-runStream :: Monad m => Stream m a -> m ()-runStream = S.drain--{-# INLINE toList #-}-toList :: Monad m => Stream m Int -> m [Int]--{-# INLINE evalStateT #-}-evalStateT :: S.MonadAsync m => Int -> Stream m Int-evalStateT n = Internal.evalStateT 0 (sourceUnfoldrState n)--{-# INLINE withState #-}-withState :: S.MonadAsync m => Int -> Stream m Int-withState n =- Internal.evalStateT (0 :: Int) (Internal.liftInner (sourceUnfoldrM n))--{-# INLINE head #-}-{-# INLINE last #-}-{-# INLINE maximum #-}-{-# INLINE minimum #-}-{-# INLINE find #-}-{-# INLINE findIndex #-}-{-# INLINE elemIndex #-}-{-# INLINE foldl1'Reduce #-}-head, last, minimum, maximum, find, findIndex, elemIndex, foldl1'Reduce- :: Monad m => Stream m Int -> m (Maybe Int)--{-# INLINE minimumBy #-}-{-# INLINE maximumBy #-}-minimumBy, maximumBy :: Monad m => Stream m Int -> m (Maybe Int)--{-# INLINE foldl'Reduce #-}-{-# INLINE foldl'ReduceMap #-}-{-# INLINE foldlM'Reduce #-}-{-# INLINE foldrMReduce #-}-{-# INLINE length #-}-{-# INLINE sum #-}-{-# INLINE product #-}-foldl'Reduce, foldl'ReduceMap, foldlM'Reduce, foldrMReduce, length, sum, product- :: Monad m- => Stream m Int -> m Int--{-# INLINE foldl'Build #-}-{-# INLINE foldlM'Build #-}-{-# INLINE foldrMBuild #-}-foldrMBuild, foldl'Build, foldlM'Build- :: Monad m- => Stream m Int -> m [Int]--{-# INLINE all #-}-{-# INLINE any #-}-{-# INLINE and #-}-{-# INLINE or #-}-{-# INLINE null #-}-{-# INLINE elem #-}-{-# INLINE notElem #-}-null, elem, notElem, all, any, and, or :: Monad m => Stream m Int -> m Bool--{-# INLINE toNull #-}-toNull :: Monad m => (t m a -> S.SerialT m a) -> t m a -> m ()-toNull t = runStream . t--{-# INLINE uncons #-}-uncons :: Monad m => Stream m Int -> m ()-uncons s = do- r <- S.uncons s- case r of- Nothing -> return ()- Just (_, t) -> uncons t--{-# INLINE init #-}-init :: Monad m => Stream m a -> m ()-init s = S.init s >>= Prelude.mapM_ S.drain--{-# INLINE tail #-}-tail :: Monad m => Stream m a -> m ()-tail s = S.tail s >>= Prelude.mapM_ tail--{-# INLINE nullHeadTail #-}-nullHeadTail :: Monad m => Stream m Int -> m ()-nullHeadTail s = do- r <- S.null s- when (not r) $ do- _ <- S.head s- S.tail s >>= Prelude.mapM_ nullHeadTail--{-# INLINE mapM_ #-}-mapM_ :: Monad m => Stream m Int -> m ()-mapM_ = S.mapM_ (\_ -> return ())--toList = S.toList--{-# INLINE toListRev #-}-toListRev :: Monad m => Stream m Int -> m [Int]-toListRev = Internal.toListRev--foldrMBuild = S.foldrM (\x xs -> xs >>= return . (x :)) (return [])-foldl'Build = S.foldl' (flip (:)) []-foldlM'Build = S.foldlM' (\xs x -> return $ x : xs) []--foldrMReduce = S.foldrM (\x xs -> xs >>= return . (x +)) (return 0)-foldl'Reduce = S.foldl' (+) 0-foldl'ReduceMap = P.fmap (+1) . S.foldl' (+) 0-foldl1'Reduce = S.foldl1' (+)-foldlM'Reduce = S.foldlM' (\xs a -> return $ a + xs) 0--last = S.last-null = S.null-head = S.head-elem = S.elem maxValue-notElem = S.notElem maxValue-length = S.length-all = S.all (<= maxValue)-any = S.any (> maxValue)-and = S.and . S.map (<= maxValue)-or = S.or . S.map (> maxValue)-find = S.find (== maxValue)-findIndex = S.findIndex (== maxValue)-elemIndex = S.elemIndex maxValue-maximum = S.maximum-minimum = S.minimum-sum = S.sum-product = S.product-minimumBy = S.minimumBy compare-maximumBy = S.maximumBy compare------------------------------------------------------------------------------------ Transformation----------------------------------------------------------------------------------{-# INLINE transform #-}-transform :: Monad m => Stream m a -> m ()-transform = runStream--{-# INLINE composeN #-}-composeN- :: MonadIO m- => Int -> (Stream m Int -> Stream m Int) -> Stream m Int -> m ()-composeN n f =- case n of- 1 -> transform . f- 2 -> transform . f . f- 3 -> transform . f . f . f- 4 -> transform . f . f . f . f- _ -> undefined---- polymorphic stream version of composeN-{-# INLINE composeN' #-}-composeN'- :: (S.IsStream t, Monad m)- => Int -> (t m Int -> Stream m Int) -> t m Int -> m ()-composeN' n f =- case n of- 1 -> transform . f- 2 -> transform . f . S.adapt . f- 3 -> transform . f . S.adapt . f . S.adapt . f- 4 -> transform . f . S.adapt . f . S.adapt . f . S.adapt . f- _ -> undefined--{-# INLINE scan #-}-{-# INLINE scanl1' #-}-{-# INLINE map #-}-{-# INLINE fmap #-}-{-# INLINE mapMaybe #-}-{-# INLINE filterEven #-}-{-# INLINE filterAllOut #-}-{-# INLINE filterAllIn #-}-{-# INLINE takeOne #-}-{-# INLINE takeAll #-}-{-# INLINE takeWhileTrue #-}-{-# INLINE takeWhileMTrue #-}-{-# INLINE dropOne #-}-{-# INLINE dropAll #-}-{-# INLINE dropWhileTrue #-}-{-# INLINE dropWhileMTrue #-}-{-# INLINE dropWhileFalse #-}-{-# INLINE findIndices #-}-{-# INLINE elemIndices #-}-{-# INLINE insertBy #-}-{-# INLINE deleteBy #-}-{-# INLINE reverse #-}-{-# INLINE reverse' #-}-{-# INLINE foldrS #-}-{-# INLINE foldrSMap #-}-{-# INLINE foldrT #-}-{-# INLINE foldrTMap #-}-scan, scanl1', map, fmap, mapMaybe, filterEven, filterAllOut,- filterAllIn, takeOne, takeAll, takeWhileTrue, takeWhileMTrue, dropOne,- dropAll, dropWhileTrue, dropWhileMTrue, dropWhileFalse,- findIndices, elemIndices, insertBy, deleteBy, reverse, reverse',- foldrS, foldrSMap, foldrT, foldrTMap- :: MonadIO m- => Int -> Stream m Int -> m ()--{-# INLINE mapMaybeM #-}-{-# INLINE intersperse #-}-mapMaybeM, intersperse :: S.MonadAsync m => Int -> Stream m Int -> m ()--{-# INLINE mapM #-}-{-# INLINE map' #-}-{-# INLINE fmap' #-}-mapM, map' :: (S.IsStream t, S.MonadAsync m)- => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()--fmap' :: (S.IsStream t, S.MonadAsync m, P.Functor (t m))- => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()--{-# INLINE sequence #-}-sequence :: (S.IsStream t, S.MonadAsync m)- => (t m Int -> S.SerialT m Int) -> t m (m Int) -> m ()--scan n = composeN n $ S.scanl' (+) 0-scanl1' n = composeN n $ S.scanl1' (+)-fmap n = composeN n $ Prelude.fmap (+1)-fmap' t n = composeN' n $ t . Prelude.fmap (+1)-map n = composeN n $ S.map (+1)-map' t n = composeN' n $ t . S.map (+1)-mapM t n = composeN' n $ t . S.mapM return--{-# INLINE transformMapM #-}-{-# INLINE transformComposeMapM #-}-{-# INLINE transformTeeMapM #-}-{-# INLINE transformZipMapM #-}--transformMapM, transformComposeMapM, transformTeeMapM,- transformZipMapM :: (S.IsStream t, S.MonadAsync m)- => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()--transformMapM t n = composeN' n $ t . Internal.transform (Pipe.mapM return)-transformComposeMapM t n = composeN' n $ t . Internal.transform- (Pipe.mapM (\x -> return (x + 1))- `Pipe.compose` Pipe.mapM (\x -> return (x + 2)))-transformTeeMapM t n = composeN' n $ t . Internal.transform- (Pipe.mapM (\x -> return (x + 1))- `Pipe.tee` Pipe.mapM (\x -> return (x + 2)))-transformZipMapM t n = composeN' n $ t . Internal.transform- (Pipe.zipWith (+) (Pipe.mapM (\x -> return (x + 1)))- (Pipe.mapM (\x -> return (x + 2))))--mapMaybe n = composeN n $ S.mapMaybe- (\x -> if Prelude.odd x then Nothing else Just x)-mapMaybeM n = composeN n $ S.mapMaybeM- (\x -> if Prelude.odd x then return Nothing else return $ Just x)-sequence t = transform . t . S.sequence-filterEven n = composeN n $ S.filter even-filterAllOut n = composeN n $ S.filter (> maxValue)-filterAllIn n = composeN n $ S.filter (<= maxValue)-takeOne n = composeN n $ S.take 1-takeAll n = composeN n $ S.take maxValue-takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue)-takeWhileMTrue n = composeN n $ S.takeWhileM (return . (<= maxValue))-dropOne n = composeN n $ S.drop 1-dropAll n = composeN n $ S.drop maxValue-dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue)-dropWhileMTrue n = composeN n $ S.dropWhileM (return . (<= maxValue))-dropWhileFalse n = composeN n $ S.dropWhile (> maxValue)-findIndices n = composeN n $ S.findIndices (== maxValue)-elemIndices n = composeN n $ S.elemIndices maxValue-intersperse n = composeN n $ S.intersperse maxValue-insertBy n = composeN n $ S.insertBy compare maxValue-deleteBy n = composeN n $ S.deleteBy (>=) maxValue-reverse n = composeN n $ S.reverse-reverse' n = composeN n $ Internal.reverse'-foldrS n = composeN n $ Internal.foldrS S.cons S.nil-foldrSMap n = composeN n $ Internal.foldrS (\x xs -> x + 1 `S.cons` xs) S.nil-foldrT n = composeN n $ Internal.foldrT S.cons S.nil-foldrTMap n = composeN n $ Internal.foldrT (\x xs -> x + 1 `S.cons` xs) S.nil------------------------------------------------------------------------------------ Iteration----------------------------------------------------------------------------------iterStreamLen, maxIters :: Int-iterStreamLen = 10-maxIters = 10000--{-# INLINE iterateSource #-}-iterateSource- :: S.MonadAsync m- => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int-iterateSource g i n = f i (sourceUnfoldrMN iterStreamLen n)- where- f (0 :: Int) m = g m- f x m = g (f (x P.- 1) m)--{-# INLINE iterateMapM #-}-{-# INLINE iterateScan #-}-{-# INLINE iterateScanl1 #-}-{-# INLINE iterateFilterEven #-}-{-# INLINE iterateTakeAll #-}-{-# INLINE iterateDropOne #-}-{-# INLINE iterateDropWhileFalse #-}-{-# INLINE iterateDropWhileTrue #-}-iterateMapM, iterateScan, iterateScanl1, iterateFilterEven, iterateTakeAll,- iterateDropOne, iterateDropWhileFalse, iterateDropWhileTrue- :: S.MonadAsync m- => Int -> Stream m Int---- this is quadratic-iterateScan = iterateSource (S.scanl' (+) 0) (maxIters `div` 10)--- so is this-iterateScanl1 = iterateSource (S.scanl1' (+)) (maxIters `div` 10)--iterateMapM = iterateSource (S.mapM return) maxIters-iterateFilterEven = iterateSource (S.filter even) maxIters-iterateTakeAll = iterateSource (S.take maxValue) maxIters-iterateDropOne = iterateSource (S.drop 1) maxIters-iterateDropWhileFalse = iterateSource (S.dropWhile (> maxValue)) maxIters-iterateDropWhileTrue = iterateSource (S.dropWhile (<= maxValue)) maxIters------------------------------------------------------------------------------------ Zipping and concat----------------------------------------------------------------------------------{-# INLINE zip #-}-zip :: Int -> Int -> IO ()-zip count n =- S.drain $ S.zipWith (,)- (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'zip-inspect $ 'zip `hasNoType` ''D.Step-#endif--{-# INLINE zipM #-}-zipM :: Int -> Int -> IO ()-zipM count n =- S.drain $ S.zipWithM (curry return)- (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'zipM-inspect $ 'zipM `hasNoType` ''D.Step-#endif--{-# INLINE mergeBy #-}-mergeBy :: Int -> Int -> IO ()-mergeBy count n =- S.drain $ S.mergeBy P.compare- (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'mergeBy-inspect $ 'mergeBy `hasNoType` ''D.Step-#endif--{-# INLINE serial2 #-}-serial2 :: Int -> Int -> IO ()-serial2 count n =- S.drain $ S.serial- (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))--{-# INLINE serial4 #-}-serial4 :: Int -> Int -> IO ()-serial4 count n =- S.drain $ S.serial- ((S.serial (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))))- ((S.serial (sourceUnfoldrMN count (n+2))- (sourceUnfoldrMN count (n + 3))))--{-# INLINE append2 #-}-append2 :: Int -> Int -> IO ()-append2 count n =- S.drain $ Internal.append- (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))--{-# INLINE append4 #-}-append4 :: Int -> Int -> IO ()-append4 count n =- S.drain $ Internal.append- ((Internal.append (sourceUnfoldrMN count n)- (sourceUnfoldrMN count (n + 1))))- ((Internal.append (sourceUnfoldrMN count (n+2))- (sourceUnfoldrMN count (n + 3))))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'append2-inspect $ 'append2 `hasNoType` ''D.AppendState-#endif--{-# INLINE wSerial2 #-}-wSerial2 :: Int -> IO ()-wSerial2 n = S.drain $ S.wSerial- (sourceUnfoldrMN (value `div` 2) n)- (sourceUnfoldrMN (value `div` 2) (n + 1))--{-# INLINE interleave2 #-}-interleave2 :: Int -> IO ()-interleave2 n = S.drain $ Internal.interleave- (sourceUnfoldrMN (value `div` 2) n)- (sourceUnfoldrMN (value `div` 2) (n + 1))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'interleave2-inspect $ 'interleave2 `hasNoType` ''D.InterleaveState-#endif--{-# INLINE roundRobin2 #-}-roundRobin2 :: Int -> IO ()-roundRobin2 n = S.drain $ Internal.roundrobin- (sourceUnfoldrMN (value `div` 2) n)- (sourceUnfoldrMN (value `div` 2) (n + 1))--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'roundRobin2-inspect $ 'roundRobin2 `hasNoType` ''D.InterleaveState-#endif--{-# INLINE isPrefixOf #-}-{-# INLINE isSubsequenceOf #-}-isPrefixOf, isSubsequenceOf :: Monad m => Stream m Int -> m Bool--isPrefixOf src = S.isPrefixOf src src-isSubsequenceOf src = S.isSubsequenceOf src src--{-# INLINE stripPrefix #-}-stripPrefix :: Monad m => Stream m Int -> m ()-stripPrefix src = do- _ <- S.stripPrefix src src- return ()--{-# INLINE zipAsync #-}-{-# INLINE zipAsyncM #-}-{-# INLINE zipAsyncAp #-}-zipAsync, zipAsyncAp, zipAsyncM :: S.MonadAsync m => Stream m Int -> m ()--zipAsync src = do- r <- S.tail src- let src1 = fromJust r- transform (S.zipAsyncWith (,) src src1)--zipAsyncM src = do- r <- S.tail src- let src1 = fromJust r- transform (S.zipAsyncWithM (curry return) src src1)--zipAsyncAp src = do- r <- S.tail src- let src1 = fromJust r- transform (S.zipAsyncly $ (,) <$> S.serially src- <*> S.serially src1)--{-# INLINE eqBy' #-}-eqBy' :: (Monad m, P.Eq a) => Stream m a -> m P.Bool-eqBy' src = S.eqBy (==) src src--{-# INLINE eqBy #-}-eqBy :: Int -> IO Bool-eqBy n = eqBy' (source n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'eqBy-inspect $ 'eqBy `hasNoType` ''D.Step-#endif---{-# INLINE eqByPure #-}-eqByPure :: Int -> Identity Bool-eqByPure n = eqBy' (sourceUnfoldr n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'eqByPure-inspect $ 'eqByPure `hasNoType` ''D.Step-#endif--{-# INLINE cmpBy' #-}-cmpBy' :: (Monad m, P.Ord a) => Stream m a -> m P.Ordering-cmpBy' src = S.cmpBy P.compare src src--{-# INLINE cmpBy #-}-cmpBy :: Int -> IO P.Ordering-cmpBy n = cmpBy' (source n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'cmpBy-inspect $ 'cmpBy `hasNoType` ''D.Step-#endif--{-# INLINE cmpByPure #-}-cmpByPure :: Int -> Identity P.Ordering-cmpByPure n = cmpBy' (sourceUnfoldr n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'cmpByPure-inspect $ 'cmpByPure `hasNoType` ''D.Step-#endif--{-# INLINE concatMap #-}-concatMap :: Int -> Int -> Int -> IO ()-concatMap outer inner n =- S.drain $ S.concatMap- (\_ -> sourceUnfoldrMN inner n)- (sourceUnfoldrMN outer n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMap-#endif--{-# INLINE concatMapPure #-}-concatMapPure :: Int -> Int -> Int -> IO ()-concatMapPure outer inner n =- S.drain $ S.concatMap- (\_ -> sourceUnfoldrN inner n)- (sourceUnfoldrN outer n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMapPure-#endif--{-# INLINE concatMapRepl4xN #-}-concatMapRepl4xN :: Int -> IO ()-concatMapRepl4xN n = S.drain $ S.concatMap (S.replicate 4)- (sourceUnfoldrMN (value `div` 4) n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMapRepl4xN-#endif--{-# INLINE concatUnfoldRepl4xN #-}-concatUnfoldRepl4xN :: Int -> IO ()-concatUnfoldRepl4xN n =- S.drain $ S.concatUnfold- (UF.replicateM 4)- (sourceUnfoldrMN (value `div` 4) n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatUnfoldRepl4xN-inspect $ 'concatUnfoldRepl4xN `hasNoType` ''D.ConcatMapUState-#endif--{-# INLINE concatMapWithSerial #-}-concatMapWithSerial :: Int -> Int -> Int -> IO ()-concatMapWithSerial outer inner n =- S.drain $ S.concatMapWith S.serial- (\_ -> sourceUnfoldrMN inner n)- (sourceUnfoldrMN outer n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMapWithSerial-#endif--{-# INLINE concatMapWithAppend #-}-concatMapWithAppend :: Int -> Int -> Int -> IO ()-concatMapWithAppend outer inner n =- S.drain $ S.concatMapWith Internal.append- (\_ -> sourceUnfoldrMN inner n)- (sourceUnfoldrMN outer n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMapWithAppend-#endif--{-# INLINE concatMapWithWSerial #-}-concatMapWithWSerial :: Int -> Int -> Int -> IO ()-concatMapWithWSerial outer inner n =- S.drain $ S.concatMapWith S.wSerial- (\_ -> sourceUnfoldrMN inner n)- (sourceUnfoldrMN outer n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatMapWithWSerial-#endif--{-# INLINE concatUnfoldInterleaveRepl4xN #-}-concatUnfoldInterleaveRepl4xN :: Int -> IO ()-concatUnfoldInterleaveRepl4xN n =- S.drain $ Internal.concatUnfoldInterleave- (UF.replicateM 4)- (sourceUnfoldrMN (value `div` 4) n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatUnfoldInterleaveRepl4xN--- inspect $ 'concatUnfoldInterleaveRepl4xN `hasNoType` ''D.ConcatUnfoldInterleaveState-#endif--{-# INLINE concatUnfoldRoundrobinRepl4xN #-}-concatUnfoldRoundrobinRepl4xN :: Int -> IO ()-concatUnfoldRoundrobinRepl4xN n =- S.drain $ Internal.concatUnfoldRoundrobin- (UF.replicateM 4)- (sourceUnfoldrMN (value `div` 4) n)--#ifdef INSPECTION-inspect $ hasNoTypeClasses 'concatUnfoldRoundrobinRepl4xN--- inspect $ 'concatUnfoldRoundrobinRepl4xN `hasNoType` ''D.ConcatUnfoldInterleaveState-#endif------------------------------------------------------------------------------------ Mixed Composition----------------------------------------------------------------------------------{-# INLINE scanMap #-}-{-# INLINE dropMap #-}-{-# INLINE dropScan #-}-{-# INLINE takeDrop #-}-{-# INLINE takeScan #-}-{-# INLINE takeMap #-}-{-# INLINE filterDrop #-}-{-# INLINE filterTake #-}-{-# INLINE filterScan #-}-{-# INLINE filterScanl1 #-}-{-# INLINE filterMap #-}-scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop,- filterTake, filterScan, filterScanl1, filterMap- :: MonadIO m => Int -> Stream m Int -> m ()--scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0-dropMap n = composeN n $ S.map (subtract 1) . S.drop 1-dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1-takeDrop n = composeN n $ S.drop 1 . S.take maxValue-takeScan n = composeN n $ S.scanl' (+) 0 . S.take maxValue-takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue-filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue)-filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue)-filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound)-filterScanl1 n = composeN n $ S.scanl1' (+) . S.filter (<= maxBound)-filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue)--data Pair a b = Pair !a !b deriving (Generic, NFData)--{-# INLINE sumProductFold #-}-sumProductFold :: Monad m => Stream m Int -> m (Int, Int)-sumProductFold = S.foldl' (\(s,p) x -> (s + x, p P.* x)) (0,1)--{-# INLINE sumProductScan #-}-sumProductScan :: Monad m => Stream m Int -> m (Pair Int Int)-sumProductScan = S.foldl' (\(Pair _ p) (s0,x) -> Pair s0 (p P.* x)) (Pair 0 1)- . S.scanl' (\(s,_) x -> (s + x,x)) (0,0)------------------------------------------------------------------------------------ Pure stream operations----------------------------------------------------------------------------------{-# INLINE eqInstance #-}-eqInstance :: Stream Identity Int -> Bool-eqInstance src = src == src--{-# INLINE eqInstanceNotEq #-}-eqInstanceNotEq :: Stream Identity Int -> Bool-eqInstanceNotEq src = src P./= src--{-# INLINE ordInstance #-}-ordInstance :: Stream Identity Int -> Bool-ordInstance src = src P.< src--{-# INLINE ordInstanceMin #-}-ordInstanceMin :: Stream Identity Int -> Stream Identity Int-ordInstanceMin src = P.min src src--{-# INLINE showInstance #-}-showInstance :: Stream Identity Int -> P.String-showInstance src = P.show src--{-# INLINE showInstanceList #-}-showInstanceList :: [Int] -> P.String-showInstanceList src = P.show src--{-# INLINE readInstance #-}-readInstance :: P.String -> Stream Identity Int-readInstance str =- let r = P.reads str- in case r of- [(x,"")] -> x- _ -> P.error "readInstance: no parse"--{-# INLINE readInstanceList #-}-readInstanceList :: P.String -> [Int]-readInstanceList str =- let r = P.reads str- in case r of- [(x,"")] -> x- _ -> P.error "readInstance: no parse"--{-# INLINE pureFoldl' #-}-pureFoldl' :: Stream Identity Int -> Int-pureFoldl' = runIdentity . S.foldl' (+) 0--{-# INLINE foldableFoldl' #-}-foldableFoldl' :: Stream Identity Int -> Int-foldableFoldl' = F.foldl' (+) 0--{-# INLINE foldableSum #-}-foldableSum :: Stream Identity Int -> Int-foldableSum = P.sum--{-# INLINE traversableMapM #-}-traversableMapM :: Stream Identity Int -> IO (Stream Identity Int)-traversableMapM = P.mapM return
+ src/Streamly/Data/Array.hs view
@@ -0,0 +1,43 @@+-- |+-- Module : Streamly.Data.Array+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Data.Array+ ( Array++ -- * Construction+ , A.fromListN+ , A.fromList++ -- Stream Folds+ , A.fromStreamN+ , A.fromStream++ -- MonadicAPIs+ , A.writeN+ , A.write++ -- * Elimination++ , A.toStream+ , A.toStreamRev+ , A.read++ -- * Random Access++ -- * Folding Arrays+ , A.streamFold+ , A.fold++ , A.length+ )+where++import Streamly.Internal.Data.Array (Array)++import qualified Streamly.Internal.Data.Array as A
src/Streamly/Data/Fold.hs view
@@ -37,6 +37,15 @@ -- on stream types can be as efficient as transformations on 'Fold' (e.g. -- 'Streamly.Internal.Data.Fold.lmap'). --+-- = Left folds vs Right Folds+--+-- The folds in this module are left folds, therefore, even partial folds, e.g.+-- @head@ in this module, would drain the whole stream. On the other hand, the+-- partial folds in "Streamly.Prelude" module are lazy right folds and would+-- terminate as soon as the result is determined. However, the folds in this+-- module can be composed but the folds in "Streamly.Prelude" cannot be+-- composed.+-- -- = Programmer Notes -- -- > import qualified Streamly.Data.Fold as FL@@ -190,7 +199,7 @@ -- ... -- @ --- -- To compute the average of numbers in a stream without going throught he+ -- To compute the average of numbers in a stream without going through the -- stream twice: -- -- >>> let avg = (/) <$> FL.sum <*> fmap fromIntegral FL.length
+ src/Streamly/Data/Prim/Array.hs view
@@ -0,0 +1,44 @@+-- |+-- Module : Streamly.Data.Prim.Array+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Data.Prim.Array+ ( PrimArray+ , Prim++ -- * Construction+ , A.fromListN+ , A.fromList++ -- Stream Folds+ -- , A.fromStreamN+ -- , A.fromStream++ -- MonadicAPIs+ , A.writeN+ , A.write++ -- * Elimination++ -- , A.toStream+ -- , A.toStreamRev+ , A.read++ -- * Random Access++ -- * Folding Arrays+ -- , A.streamFold+ -- , A.fold++ , A.length+ )+where++import Streamly.Internal.Data.Prim.Array (PrimArray, Prim)++import qualified Streamly.Internal.Data.Prim.Array as A
+ src/Streamly/Data/SmallArray.hs view
@@ -0,0 +1,34 @@+-- |+-- Module : Streamly.Data.SmallArray+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++module Streamly.Data.SmallArray+ ( SmallArray++ -- * Construction+ , A.fromListN+ , A.fromStreamN++ , A.writeN++ -- * Elimination+ , A.toStream+ , A.toStreamRev+ , A.read++ -- * Folding Arrays+ , A.streamFold+ , A.fold++ , A.length+ )+where++import Streamly.Internal.Data.SmallArray (SmallArray)++import qualified Streamly.Internal.Data.SmallArray as A
src/Streamly/Data/Unfold.hs view
@@ -1,12 +1,9 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TupleSections #-} #include "inline.hs" @@ -22,8 +19,8 @@ -- values from a single starting value often called a seed value. Values can be -- generated and /pulled/ from the 'Unfold' one at a time. It can also be -- called a producer or a source of stream. It is a data representation of the--- standard 'S.unfoldr' function. An 'Unfold' can be converted into a stream--- type using 'S.unfold' by supplying the seed.+-- standard 'Streamly.Prelude.unfoldr' function. An 'Unfold' can be converted+-- into a stream type using 'Streamly.Prelude.unfold' by supplying the seed. -- -- = Performance Notes --@@ -34,13 +31,13 @@ -- @Unfold m a b@ can be considered roughly equivalent to an action @a -> t m -- b@ (where @t@ is a stream type). Instead of using an 'Unfold' one could just -- use a function of the shape @a -> t m b@. However, working with stream types--- like 'S.SerialT' does not allow the compiler to perform stream fusion+-- like t'Streamly.SerialT' does not allow the compiler to perform stream fusion -- optimization when merging, appending or concatenating multiple streams. -- Even though stream based combinator have excellent performance, they are -- much less efficient when compared to combinators using 'Unfold'. For--- example, the 'S.concatMap' combinator which uses @a -> t m b@ (where @t@ is--- a stream type) to generate streams is much less efficient compared to--- 'S.concatUnfold'.+-- example, the 'Streamly.Prelude.concatMap' combinator which uses @a -> t m b@+-- (where @t@ is a stream type) to generate streams is much less efficient+-- compared to 'Streamly.Prelude.concatUnfold'. -- -- On the other hand, transformation operations on stream types are as -- efficient as transformations on 'Unfold'.@@ -58,8 +55,8 @@ -- More, not yet exposed, unfold combinators can be found in -- "Streamly.Internal.Data.Unfold". --- The stream types (e.g. 'S.SerialT') can be considered as a special case of--- 'Unfold' with no starting seed.+-- The stream types (e.g. t'Streamly.SerialT') can be considered as a special+-- case of 'Unfold' with no starting seed. -- module Streamly.Data.Unfold (
src/Streamly/Data/Unicode/Stream.hs view
@@ -58,8 +58,9 @@ -- -- = Experimental APIs ----- Some experimental APIs to conveniently process text using the @Array Char@--- represenation directly can be found in "Streamly.Internal.Unicode.Array".+-- Some experimental APIs to conveniently process text using the+-- @Array Char@ represenation directly can be found in+-- "Streamly.Internal.Memory.Unicode.Array". -- XXX an unpinned array representation can be useful to store short and short -- lived strings in memory.
src/Streamly/FileSystem/FD.hs view
@@ -1,8 +1,4 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} #include "inline.hs" @@ -137,12 +133,12 @@ import qualified GHC.IO.Device as RawIO import Streamly.Internal.Memory.Array.Types (Array(..), byteLength, defaultChunkSize)-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream, mkStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream) #if !defined(mingw32_HOST_OS) import Streamly.Internal.Memory.Array.Types (groupIOVecsOf)-import Streamly.Streams.StreamD (toStreamD)+import Streamly.Internal.Data.Stream.StreamD (toStreamD) import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD) import qualified Streamly.FileSystem.FDIO as RawIO hiding (write) #endif@@ -204,7 +200,7 @@ -- the same absolute path name and neither has been renamed, for example. -- openFile :: FilePath -> IOMode -> IO Handle-openFile path mode = fmap (Handle . fst) $ FD.openFile path mode True+openFile path mode = Handle . fst <$> FD.openFile path mode True ------------------------------------------------------------------------------- -- Array IO (Input)@@ -241,7 +237,7 @@ {-# INLINABLE writeArray #-} writeArray :: Storable a => Handle -> Array a -> IO () writeArray _ arr | A.length arr == 0 = return ()-writeArray (Handle fd) arr = withForeignPtr (aStart arr) $ \p -> do+writeArray (Handle fd) arr = withForeignPtr (aStart arr) $ \p -> -- RawIO.writeAll fd (castPtr p) aLen RawIO.write fd (castPtr p) aLen {-@@ -349,7 +345,7 @@ -- @since 0.7.0 {-# INLINE writeArrays #-} writeArrays :: (MonadIO m, Storable a) => Handle -> SerialT m (Array a) -> m ()-writeArrays h m = S.mapM_ (liftIO . writeArray h) m+writeArrays h = S.mapM_ (liftIO . writeArray h) -- | Write a stream of arrays to a handle after coalescing them in chunks of -- specified size. The chunk size is only a maximum and the actual writes could@@ -369,7 +365,7 @@ -- @since 0.7.0 {-# INLINE writev #-} writev :: MonadIO m => Handle -> SerialT m (Array RawIO.IOVec) -> m ()-writev h m = S.mapM_ (liftIO . writeIOVec h) m+writev h = S.mapM_ (liftIO . writeIOVec h) -- XXX this is incomplete -- | Write a stream of arrays to a handle after grouping them in 'IOVec' arrays
src/Streamly/FileSystem/FDIO.hs view
@@ -1,8 +1,5 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} #include "inline.hs"
src/Streamly/FileSystem/Handle.hs view
@@ -1,8 +1,4 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} #include "inline.hs"
+ src/Streamly/Internal/BaseCompat.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE CPP #-}++-- |+-- Module : Streamly.Internal.BaseCompat+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Compatibility functions for "base" package.+--+module Streamly.Internal.BaseCompat+ (+ (#.)+ , errorWithoutStackTrace+ )+where++import Data.Coerce (Coercible, coerce)++{-# INLINE (#.) #-}+(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)+(#.) _f = coerce++#if !(MIN_VERSION_base(4,9,0))+{-# NOINLINE errorWithoutStackTrace #-}+errorWithoutStackTrace :: [Char] -> a+errorWithoutStackTrace s = error s+#endif
+ src/Streamly/Internal/Control/Monad.hs view
@@ -0,0 +1,28 @@+-- |+-- Module : Streamly.Internal.Control.Monad+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Additional "Control.Monad" utilities.++{-# LANGUAGE ScopedTypeVariables #-}++module Streamly.Internal.Control.Monad+ ( discard+ )+where++import Control.Monad (void)+import Control.Monad.Catch (MonadCatch, catch, SomeException)++-- | Discard any exceptions or value returned by an effectful action.+--+-- /Internal/+--+{-# INLINE discard #-}+discard :: MonadCatch m => m b -> m ()+discard action = (void $ action) `catch` (\(_ :: SomeException) -> return ())
+ src/Streamly/Internal/Data/Array.hs view
@@ -0,0 +1,203 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Array+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Array+ ( Array(..)++ , foldl'+ , foldr++ , length++ , writeN+ , write++ , toStreamD+ , toStreamDRev++ , toStream+ , toStreamRev+ , read++ , fromListN+ , fromList+ , fromStreamDN+ , fromStreamD++ , fromStreamN+ , fromStream++ , streamFold+ , fold+ )+where++import Prelude hiding (foldr, length, read)+import Control.DeepSeq (NFData(..))+import Control.Monad (when)+import Control.Monad.IO.Class (liftIO, MonadIO)+import GHC.IO (unsafePerformIO)+import GHC.Base (Int(..))+import Data.Functor.Identity (runIdentity)+import Data.Primitive.Array hiding (fromList, fromListN)+import qualified GHC.Exts as Exts++import Streamly.Internal.Data.Unfold.Types (Unfold(..))+import Streamly.Internal.Data.Fold.Types (Fold(..))+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)++import qualified Streamly.Internal.Data.Stream.StreamD as D++{-# NOINLINE bottomElement #-}+bottomElement :: a+bottomElement = undefined++{-# INLINE_NORMAL toStreamD #-}+toStreamD :: Monad m => Array a -> D.Stream m a+toStreamD arr = D.Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i == length arr = return D.Stop+ step _ (I# i) =+ return $+ case Exts.indexArray# (array# arr) i of+ (# x #) -> D.Yield x ((I# i) + 1)++{-# INLINE length #-}+length :: Array a -> Int+length arr = sizeofArray arr++{-# INLINE_NORMAL toStreamDRev #-}+toStreamDRev :: Monad m => Array a -> D.Stream m a+toStreamDRev arr = D.Stream step (length arr - 1)+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i < 0 = return D.Stop+ step _ (I# i) =+ return $+ case Exts.indexArray# (array# arr) i of+ (# x #) -> D.Yield x ((I# i) - 1)++{-# INLINE_NORMAL foldl' #-}+foldl' :: (b -> a -> b) -> b -> Array a -> b+foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr++{-# INLINE_NORMAL foldr #-}+foldr :: (a -> b -> b) -> b -> Array a -> b+foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr++-- writeN n = S.evertM (fromStreamDN n)+{-# INLINE_NORMAL writeN #-}+writeN :: MonadIO m => Int -> Fold m a (Array a)+writeN limit = Fold step initial extract+ where+ initial = do+ marr <- liftIO $ newArray limit bottomElement+ return (marr, 0)+ step (marr, i) x+ | i == limit = return (marr, i)+ | otherwise = do+ liftIO $ writeArray marr i x+ return (marr, i + 1)+ extract (marr, len) = liftIO $ freezeArray marr 0 len++{-# INLINE_NORMAL write #-}+write :: MonadIO m => Fold m a (Array a)+write = Fold step initial extract+ where+ initial = do+ marr <- liftIO $ newArray 0 bottomElement+ return (marr, 0, 0)+ step (marr, i, capacity) x+ | i == capacity =+ let newCapacity = max (capacity * 2) 1+ in do newMarr <- liftIO $ newArray newCapacity bottomElement+ liftIO $ copyMutableArray newMarr 0 marr 0 i+ liftIO $ writeArray newMarr i x+ return (newMarr, i + 1, newCapacity)+ | otherwise = do+ liftIO $ writeArray marr i x+ return (marr, i + 1, capacity)+ extract (marr, len, _) = liftIO $ freezeArray marr 0 len++{-# INLINE_NORMAL fromStreamDN #-}+fromStreamDN :: MonadIO m => Int -> D.Stream m a -> m (Array a)+fromStreamDN limit str = do+ marr <- liftIO $ newArray (max limit 0) bottomElement+ i <-+ D.foldlM'+ (\i x -> i `seq` (liftIO $ writeArray marr i x) >> return (i + 1))+ 0 $+ D.take limit str+ liftIO $ freezeArray marr 0 i++{-# INLINE fromStreamD #-}+fromStreamD :: MonadIO m => D.Stream m a -> m (Array a)+fromStreamD str = D.runFold write str++{-# INLINABLE fromListN #-}+fromListN :: Int -> [a] -> Array a+fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs++{-# INLINABLE fromList #-}+fromList :: [a] -> Array a+fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs++instance NFData a => NFData (Array a) where+ {-# INLINE rnf #-}+ rnf = foldl' (\_ x -> rnf x) ()++{-# INLINE fromStreamN #-}+fromStreamN :: MonadIO m => Int -> SerialT m a -> m (Array a)+fromStreamN n m = do+ when (n < 0) $ error "fromStreamN: negative write count specified"+ fromStreamDN n $ D.toStreamD m++{-# INLINE fromStream #-}+fromStream :: MonadIO m => SerialT m a -> m (Array a)+fromStream m = fromStreamD $ D.toStreamD m++{-# INLINE_EARLY toStream #-}+toStream :: (Monad m, IsStream t) => Array a -> t m a+toStream = D.fromStreamD . toStreamD++{-# INLINE_EARLY toStreamRev #-}+toStreamRev :: (Monad m, IsStream t) => Array a -> t m a+toStreamRev = D.fromStreamD . toStreamDRev++{-# INLINE fold #-}+fold :: Monad m => Fold m a b -> Array a -> m b+fold f arr = D.runFold f (toStreamD arr)++{-# INLINE streamFold #-}+streamFold :: Monad m => (SerialT m a -> m b) -> Array a -> m b+streamFold f arr = f (toStream arr)++{-# INLINE_NORMAL read #-}+read :: Monad m => Unfold m (Array a) a+read = Unfold step inject+ where+ inject arr = return (arr, 0)+ step (arr, i)+ | i == length arr = return D.Stop+ step (arr, (I# i)) =+ return $+ case Exts.indexArray# (array# arr) i of+ (# x #) -> D.Yield x (arr, I# i + 1)
src/Streamly/Internal/Data/Atomics.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} -- |
src/Streamly/Internal/Data/Fold.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-}@@ -56,7 +55,7 @@ , stdDev , rollingHash , rollingHashWithSalt- -- , rollingHashFirstN+ , rollingHashFirstN -- , rollingHashLastN -- ** Full Folds (Monoidal)@@ -70,8 +69,8 @@ , toListRevF -- experimental -- ** Partial Folds- -- , drainN- -- , drainWhile+ , drainN+ , drainWhile -- , lastN -- , (!!) -- , genericIndex@@ -147,6 +146,7 @@ , tee , distribute+ , distribute_ -- * Partitioning @@ -159,7 +159,9 @@ , demux -- , demuxWith , demux_+ , demuxDefault_ -- , demuxWith_+ , demuxWithDefault_ -- * Classifying @@ -180,10 +182,15 @@ -- , concatMap -- , chunksOf , duplicate -- experimental++ -- * Folding to SVar+ , toParallelSVar+ , toParallelSVarLimited ) where import Control.Monad (void)+import Control.Monad.IO.Class (MonadIO(..)) import Data.Functor.Identity (Identity(..)) import Data.Map.Strict (Map) @@ -201,6 +208,7 @@ import Streamly.Internal.Data.Pipe.Types (Pipe (..), PipeState(..)) import Streamly.Internal.Data.Fold.Types import Streamly.Internal.Data.Strict+import Streamly.Internal.Data.SVar import qualified Streamly.Internal.Data.Pipe.Types as Pipe @@ -340,7 +348,7 @@ -- from the 'Foldable', the result is 'None' for empty containers. {-# INLINABLE _Fold1 #-} _Fold1 :: Monad m => (a -> a -> a) -> Fold m a (Maybe a)-_Fold1 step = Fold step_ (return Nothing') fromStrictMaybe+_Fold1 step = Fold step_ (return Nothing') (return . toMaybe) where step_ mx a = return $ Just' $ case mx of@@ -555,6 +563,14 @@ rollingHash :: (Monad m, Enum a) => Fold m a Int rollingHash = rollingHashWithSalt defaultSalt +-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of+-- a stream.+--+-- > rollingHashFirstN = ltake n rollingHash+{-# INLINABLE rollingHashFirstN #-}+rollingHashFirstN :: (Monad m, Enum a) => Int -> Fold m a Int+rollingHashFirstN n = ltake n rollingHash+ ------------------------------------------------------------------------------ -- Monoidal left folds ------------------------------------------------------------------------------@@ -608,7 +624,7 @@ -- | Folds the input stream to a list. -- -- /Warning!/ working on large lists accumulated as buffers in memory could be--- very inefficient, consider using "Streamly.Array" instead.+-- very inefficient, consider using "Streamly.Memory.Array" instead. -- -- @since 0.7.0 @@ -623,6 +639,18 @@ -- Partial Folds ------------------------------------------------------------------------------ +-- | A fold that drains the first n elements of its input, running the effects+-- and discarding the results.+{-# INLINABLE drainN #-}+drainN :: Monad m => Int -> Fold m a ()+drainN n = ltake n drain++-- | A fold that drains elements of its input as long as the predicate succeeds,+-- running the effects and discarding the results.+{-# INLINABLE drainWhile #-}+drainWhile :: Monad m => (a -> Bool) -> Fold m a ()+drainWhile p = ltakeWhile p drain+ ------------------------------------------------------------------------------ -- To Elements ------------------------------------------------------------------------------@@ -664,7 +692,7 @@ -- @since 0.7.0 {-# INLINABLE find #-} find :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-find predicate = Fold step (return Nothing') fromStrictMaybe+find predicate = Fold step (return Nothing') (return . toMaybe) where step x a = return $ case x of@@ -679,7 +707,7 @@ -- @since 0.7.0 {-# INLINABLE lookup #-} lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)-lookup a0 = Fold step (return Nothing') fromStrictMaybe+lookup a0 = Fold step (return Nothing') (return . toMaybe) where step x (a,b) = return $ case x of@@ -839,6 +867,22 @@ distribute [] = foldNil distribute (x:xs) = foldCons x (distribute xs) +-- | Like 'distribute' but for folds that return (), this can be more efficient+-- than 'distribute' as it does not need to maintain state.+--+{-# INLINE distribute_ #-}+distribute_ :: Monad m => [Fold m a ()] -> Fold m a ()+distribute_ fs = Fold step initial extract+ where+ initial = Prelude.mapM (\(Fold s i e) ->+ i >>= \r -> return (Fold s (return r) e)) fs+ step ss a = do+ Prelude.mapM_ (\(Fold s i _) -> i >>= \r -> s r a >> return ()) ss+ return ss+ extract ss = do+ Prelude.mapM_ (\(Fold _ i e) -> i >>= \r -> e r) ss+ return ()+ ------------------------------------------------------------------------------ -- Partitioning ------------------------------------------------------------------------------@@ -978,6 +1022,8 @@ where initial = return kv+-- alterF is available only since containers version 0.5.8.2+#if MIN_VERSION_containers(0,5,8) step mp a = case f a of (k, a') -> Map.alterF twiddle k mp -- XXX should we raise an exception in Nothing case?@@ -990,6 +1036,15 @@ twiddle (Just (Fold step' acc extract')) = do !r <- acc >>= \x -> step' x a' pure . Just $ Fold step' (return r) extract'+#else+ step mp a =+ let (k, a') = f a+ in case Map.lookup k mp of+ Nothing -> return mp+ Just (Fold step' acc extract') -> do+ !r <- acc >>= \x -> step' x a'+ return $ Map.insert k (Fold step' (return r) extract') mp+#endif extract = Prelude.mapM (\(Fold _ acc e) -> acc >>= e) -- | Fold a stream of key value pairs using a map of specific folds for each@@ -1000,8 +1055,7 @@ -- > let table = Data.Map.fromList [(\"SUM", FL.sum), (\"PRODUCT", FL.product)] -- input = S.fromList [(\"SUM",1),(\"PRODUCT",2),(\"SUM",3),(\"PRODUCT",4)] -- in S.fold (FL.demux table) input--- One 1--- Two 2+-- fromList [("PRODUCT",8),("SUM",4)] -- @ -- -- @since 0.7.0@@ -1010,6 +1064,32 @@ => Map k (Fold m a b) -> Fold m (k, a) (Map k b) demux = demuxWith id +{-# INLINE demuxWithDefault_ #-}+demuxWithDefault_ :: (Monad m, Ord k)+ => (a -> (k, a')) -> Map k (Fold m a' b) -> Fold m (k, a') b -> Fold m a ()+demuxWithDefault_ f kv (Fold dstep dinitial dextract) =+ Fold step initial extract++ where++ initFold (Fold s i e) = i >>= \r -> return (Fold s (return r) e)+ initial = do+ mp <- Prelude.mapM initFold kv+ dacc <- dinitial+ return (Tuple' mp dacc)+ step (Tuple' mp dacc) a+ | (k, a') <- f a+ = case Map.lookup k mp of+ Nothing -> do+ acc <- dstep dacc (k, a')+ return (Tuple' mp acc)+ Just (Fold step' acc _) -> do+ _ <- acc >>= \x -> step' x a'+ return (Tuple' mp dacc)+ extract (Tuple' mp dacc) = do+ void $ dextract dacc+ Prelude.mapM_ (\(Fold _ acc e) -> acc >>= e) mp+ -- | Split the input stream based on a key field and fold each split using a -- specific fold without collecting the results. Useful for cases like protocol -- handlers to handle different type of packets.@@ -1068,6 +1148,11 @@ demux_ :: (Monad m, Ord k) => Map k (Fold m a ()) -> Fold m (k, a) () demux_ = demuxWith_ id +{-# INLINE demuxDefault_ #-}+demuxDefault_ :: (Monad m, Ord k)+ => Map k (Fold m a ()) -> Fold m (k, a) () -> Fold m (k, a) ()+demuxDefault_ = demuxWithDefault_ id+ -- TODO If the data is large we may need a map/hashmap in pinned memory instead -- of a regular Map. That may require a serializable constraint though. We can -- have another API for that.@@ -1207,3 +1292,47 @@ lchunksInRange low high (Fold step1 initial1 extract1) (Fold step2 initial2 extract2) = undefined -}++------------------------------------------------------------------------------+-- Fold to a Parallel SVar+------------------------------------------------------------------------------++{-# INLINE toParallelSVar #-}+toParallelSVar :: MonadIO m => SVar t m a -> Maybe WorkerInfo -> Fold m a ()+toParallelSVar svar winfo = Fold step initial extract+ where++ initial = return ()++ step () x = liftIO $ do+ -- XXX we can have a separate fold for unlimited buffer case to avoid a+ -- branch in the step here.+ decrementBufferLimit svar+ void $ send svar (ChildYield x)++ extract () = liftIO $ do+ sendStop svar winfo++{-# INLINE toParallelSVarLimited #-}+toParallelSVarLimited :: MonadIO m+ => SVar t m a -> Maybe WorkerInfo -> Fold m a ()+toParallelSVarLimited svar winfo = Fold step initial extract+ where++ initial = return True++ step True x = liftIO $ do+ yieldLimitOk <- decrementYieldLimit svar+ if yieldLimitOk+ then do+ decrementBufferLimit svar+ void $ send svar (ChildYield x)+ return True+ else do+ cleanupSVarFromWorker svar+ sendStop svar winfo+ return False+ step False _ = return False++ extract True = liftIO $ sendStop svar winfo+ extract False = return ()
src/Streamly/Internal/Data/Fold/Types.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-}
src/Streamly/Internal/Data/List.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -84,11 +83,11 @@ #endif import GHC.Exts (IsList(..), IsString(..)) -import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.Zip (ZipSerialM)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.Zip (ZipSerialM) -import qualified Streamly.Streams.Prelude as P-import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.Prelude as P+import qualified Streamly.Internal.Data.Stream.StreamK as K -- We implement list as a newtype instead of a type synonym to make type -- inference easier when using -XOverloadedLists and -XOverloadedStrings. When
src/Streamly/Internal/Data/Pipe.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-}@@ -141,7 +140,7 @@ -- ... -- @ --- -- To compute the average of numbers in a stream without going throught he+ -- To compute the average of numbers in a stream without going through the -- stream twice: -- -- >>> let avg = (/) <$> FL.sum <*> fmap fromIntegral FL.length@@ -257,8 +256,8 @@ (Pipe(..), PipeState(..), Step(..), zipWith, tee, map, compose) -- import Streamly.Internal.Memory.Array.Types (Array) -- import Streamly.Memory.Ring (Ring)--- import Streamly.Streams.Serial (SerialT)--- import Streamly.Streams.StreamK (IsStream())+-- import Streamly.Internal.Data.Stream.Serial (SerialT)+-- import Streamly.Internal.Data.Stream.StreamK (IsStream()) -- import Streamly.Internal.Data.Time.Units -- (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime, -- toAbsTime)@@ -267,9 +266,9 @@ -- import qualified Streamly.Internal.Memory.Array.Types as A -- import qualified Streamly.Prelude as S--- import qualified Streamly.Streams.StreamD as D--- import qualified Streamly.Streams.StreamK as K--- import qualified Streamly.Streams.Prelude as P+-- import qualified Streamly.Internal.Data.Stream.StreamD as D+-- import qualified Streamly.Internal.Data.Stream.StreamK as K+-- import qualified Streamly.Internal.Data.Stream.Prelude as P ------------------------------------------------------------------------------ -- Pipes@@ -1805,7 +1804,7 @@ -- All the input elements belonging to a session are collected using the fold -- @f@. The session key and the fold result are emitted in the output stream -- when the session is purged either via the session close event or via the--- session liftime timeout.+-- session lifetime timeout. -- -- @since 0.7.0 {-# INLINABLE classifySessionsBy #-}
src/Streamly/Internal/Data/Pipe/Types.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-}
+ src/Streamly/Internal/Data/Prim/Array.hs view
@@ -0,0 +1,205 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Prim.Array+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Prim.Array+ (++ -- XXX should it be just Array instead? We should be able to replace one+ -- array type with another easily.+ PrimArray(..)++ -- XXX Prim should be exported from Data.Prim module?+ , Prim(..)++ , foldl'+ , foldr++ , length++ , writeN+ , write++ , toStreamD+ , toStreamDRev++ , toStream+ , toStreamRev+ , read+ , readSlice++ , fromListN+ , fromList+ , fromStreamDN+ , fromStreamD++ , fromStreamN+ , fromStream++ , streamFold+ , fold+ )+where++import Prelude hiding (foldr, length, read)+import Control.DeepSeq (NFData(..))+import Control.Monad (when)+import Control.Monad.IO.Class (liftIO, MonadIO)+import GHC.IO (unsafePerformIO)+import Data.Primitive.Types (Prim(..))++import Streamly.Internal.Data.Prim.Array.Types+import Streamly.Internal.Data.Unfold.Types (Unfold(..))+import Streamly.Internal.Data.Fold.Types (Fold(..))+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)++import qualified Streamly.Internal.Data.Stream.StreamD as D++{-# INLINE_NORMAL toStreamD #-}+toStreamD :: (Prim a, Monad m) => PrimArray a -> D.Stream m a+toStreamD arr = D.Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i == sizeofPrimArray arr = return D.Stop+ step _ i = return $ D.Yield (indexPrimArray arr i) (i + 1)++{-# INLINE length #-}+length :: Prim a => PrimArray a -> Int+length arr = sizeofPrimArray arr++{-# INLINE_NORMAL toStreamDRev #-}+toStreamDRev :: (Prim a, Monad m) => PrimArray a -> D.Stream m a+toStreamDRev arr = D.Stream step (sizeofPrimArray arr - 1)+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i < 0 = return D.Stop+ step _ i = return $ D.Yield (indexPrimArray arr i) (i - 1)++{-# INLINE_NORMAL foldl' #-}+foldl' :: Prim a => (b -> a -> b) -> b -> PrimArray a -> b+foldl' = foldlPrimArray'++{-# INLINE_NORMAL foldr #-}+foldr :: Prim a => (a -> b -> b) -> b -> PrimArray a -> b+foldr = foldrPrimArray++-- writeN n = S.evertM (fromStreamDN n)+{-# INLINE_NORMAL writeN #-}+writeN :: (MonadIO m, Prim a) => Int -> Fold m a (PrimArray a)+writeN limit = Fold step initial extract+ where+ initial = do+ marr <- liftIO $ newPrimArray limit+ return (marr, 0)+ step (marr, i) x+ | i == limit = return (marr, i)+ | otherwise = do+ liftIO $ writePrimArray marr i x+ return (marr, i + 1)+ extract (marr, _) = liftIO $ unsafeFreezePrimArray marr++{-# INLINE_NORMAL write #-}+write :: (MonadIO m, Prim a) => Fold m a (PrimArray a)+write = Fold step initial extract+ where+ initial = do+ marr <- liftIO $ newPrimArray 0+ return (marr, 0, 0)+ step (marr, i, capacity) x+ | i == capacity =+ let newCapacity = max (capacity * 2) 1+ in do newMarr <- liftIO $ resizeMutablePrimArray marr newCapacity+ liftIO $ writePrimArray newMarr i x+ return (newMarr, i + 1, newCapacity)+ | otherwise = do+ liftIO $ writePrimArray marr i x+ return (marr, i + 1, capacity)+ extract (marr, len, _) = do liftIO $ shrinkMutablePrimArray marr len+ liftIO $ unsafeFreezePrimArray marr++{-# INLINE_NORMAL fromStreamDN #-}+fromStreamDN :: (MonadIO m, Prim a) => Int -> D.Stream m a -> m (PrimArray a)+fromStreamDN limit str = do+ marr <- liftIO $ newPrimArray (max limit 0)+ _ <-+ D.foldlM'+ (\i x -> i `seq` (liftIO $ writePrimArray marr i x) >> return (i + 1))+ 0 $+ D.take limit str+ liftIO $ unsafeFreezePrimArray marr++{-# INLINE fromStreamD #-}+fromStreamD :: (MonadIO m, Prim a) => D.Stream m a -> m (PrimArray a)+fromStreamD str = D.runFold write str++{-# INLINABLE fromListN #-}+fromListN :: Prim a => Int -> [a] -> PrimArray a+fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs++{-# INLINABLE fromList #-}+fromList :: Prim a => [a] -> PrimArray a+fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs++instance Prim a => NFData (PrimArray a) where+ {-# INLINE rnf #-}+ rnf = foldl' (\_ _ -> ()) ()++{-# INLINE fromStreamN #-}+fromStreamN :: (MonadIO m, Prim a) => Int -> SerialT m a -> m (PrimArray a)+fromStreamN n m = do+ when (n < 0) $ error "fromStreamN: negative write count specified"+ fromStreamDN n $ D.toStreamD m++{-# INLINE fromStream #-}+fromStream :: (MonadIO m, Prim a) => SerialT m a -> m (PrimArray a)+fromStream m = fromStreamD $ D.toStreamD m++{-# INLINE_EARLY toStream #-}+toStream :: (Prim a, Monad m, IsStream t) => PrimArray a -> t m a+toStream = D.fromStreamD . toStreamD++{-# INLINE_EARLY toStreamRev #-}+toStreamRev :: (Prim a, Monad m, IsStream t) => PrimArray a -> t m a+toStreamRev = D.fromStreamD . toStreamDRev++{-# INLINE fold #-}+fold :: (Prim a, Monad m) => Fold m a b -> PrimArray a -> m b+fold f arr = D.runFold f (toStreamD arr)++{-# INLINE streamFold #-}+streamFold :: (Prim a, Monad m) => (SerialT m a -> m b) -> PrimArray a -> m b+streamFold f arr = f (toStream arr)++{-# INLINE_NORMAL read #-}+read :: (Prim a, Monad m) => Unfold m (PrimArray a) a+read = Unfold step inject+ where+ inject arr = return (arr, 0)+ step (arr, i)+ | i == length arr = return D.Stop+ step (arr, i) = return $ D.Yield (indexPrimArray arr i) (arr, i + 1)++{-# INLINE_NORMAL readSlice #-}+readSlice :: (Prim a, Monad m) => Int -> Int -> Unfold m (PrimArray a) a+readSlice off len = Unfold step inject+ where+ inject arr = return (arr, off)+ step (arr, i)+ | i == min (off + len) (length arr) = return D.Stop+ step (arr, i) = return $ D.Yield (indexPrimArray arr i) (arr, i + 1)
+ src/Streamly/Internal/Data/Prim/Array/Types.hs view
@@ -0,0 +1,943 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UnboxedTuples #-}++-- |+-- Module : Streamly.Internal.Data.Prim.Array.Types+-- Copyright : (c) Roman Leshchinskiy 2009-2012+-- License : BSD-style+--+-- Maintainer : streamly@composewell.com+-- Portability : non-portable+--+-- Arrays of unboxed primitive types. The function provided by this module+-- match the behavior of those provided by @Data.Primitive.ByteArray@, and+-- the underlying types and primops that back them are the same.+-- However, the type constructors 'PrimArray' and 'MutablePrimArray' take one additional+-- argument than their respective counterparts 'ByteArray' and 'MutableByteArray'.+-- This argument is used to designate the type of element in the array.+-- Consequently, all function this modules accepts length and incides in+-- terms of elements, not bytes.+--+-- @since 0.6.4.0+module Streamly.Internal.Data.Prim.Array.Types+ ( -- * Types+ PrimArray(..)+ , MutablePrimArray(..)+ -- * Allocation+ , newPrimArray+ , resizeMutablePrimArray+ , shrinkMutablePrimArray+ -- * Element Access+ , readPrimArray+ , writePrimArray+ , indexPrimArray+ -- * Freezing and Thawing+ , unsafeFreezePrimArray+ , unsafeThawPrimArray+ -- * Block Operations+ , copyPrimArray+ , copyMutablePrimArray+ , copyPrimArrayToPtr+ , copyMutablePrimArrayToPtr+ , setPrimArray+ -- * Information+ , sameMutablePrimArray+ , getSizeofMutablePrimArray+ , sizeofMutablePrimArray+ , sizeofPrimArray+ -- * List Conversion+ , primArrayToList+ , primArrayFromList+ , primArrayFromListN+ -- * Folding+ , foldrPrimArray+ , foldrPrimArray'+ , foldlPrimArray+ , foldlPrimArray'+ , foldlPrimArrayM'+ -- * Effectful Folding+ , traversePrimArray_+ , itraversePrimArray_+ -- * Map/Create+ , mapPrimArray+ , imapPrimArray+ , generatePrimArray+ , replicatePrimArray+ , filterPrimArray+ , mapMaybePrimArray+ -- * Effectful Map/Create+ -- $effectfulMapCreate+ -- ** Lazy Applicative+ , traversePrimArray+ , itraversePrimArray+ , generatePrimArrayA+ , replicatePrimArrayA+ , filterPrimArrayA+ , mapMaybePrimArrayA+ -- ** Strict Primitive Monadic+ , traversePrimArrayP+ , itraversePrimArrayP+ , generatePrimArrayP+ , replicatePrimArrayP+ , filterPrimArrayP+ , mapMaybePrimArrayP+ ) where++import GHC.Exts++import Data.Primitive.Types+import Data.Primitive.ByteArray (ByteArray(..))+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid (Monoid(..),(<>))+#endif+import Control.Applicative+import Control.Monad.Primitive+import Control.Monad.ST+import qualified Data.List as L+import qualified Data.Primitive.ByteArray as PB+import qualified Data.Primitive.Types as PT++#if MIN_VERSION_base(4,9,0) && !MIN_VERSION_base(4,11,0)+import Data.Semigroup (Semigroup)+#endif+#if MIN_VERSION_base(4,9,0)+import qualified Data.Semigroup as SG+#endif++-- | Arrays of unboxed elements. This accepts types like 'Double', 'Char',+-- 'Int', and 'Word', as well as their fixed-length variants ('Word8',+-- 'Word16', etc.). Since the elements are unboxed, a 'PrimArray' is strict+-- in its elements. This differs from the behavior of 'Array', which is lazy+-- in its elements.+data PrimArray a = PrimArray ByteArray#++-- | Mutable primitive arrays associated with a primitive state token.+-- These can be written to and read from in a monadic context that supports+-- sequencing such as 'IO' or 'ST'. Typically, a mutable primitive array will+-- be built and then convert to an immutable primitive array using+-- 'unsafeFreezePrimArray'. However, it is also acceptable to simply discard+-- a mutable primitive array since it lives in managed memory and will be+-- garbage collected when no longer referenced.+data MutablePrimArray s a = MutablePrimArray (MutableByteArray# s)++sameByteArray :: ByteArray# -> ByteArray# -> Bool+sameByteArray ba1 ba2 =+ case reallyUnsafePtrEquality# (unsafeCoerce# ba1 :: ()) (unsafeCoerce# ba2 :: ()) of+ r -> isTrue# r++-- | @since 0.6.4.0+instance (Eq a, Prim a) => Eq (PrimArray a) where+ a1@(PrimArray ba1#) == a2@(PrimArray ba2#)+ | sameByteArray ba1# ba2# = True+ | sz1 /= sz2 = False+ | otherwise = loop (quot sz1 (sizeOf (undefined :: a)) - 1)+ where+ -- Here, we take the size in bytes, not in elements. We do this+ -- since it allows us to defer performing the division to+ -- calculate the size in elements.+ sz1 = PB.sizeofByteArray (ByteArray ba1#)+ sz2 = PB.sizeofByteArray (ByteArray ba2#)+ loop !i+ | i < 0 = True+ | otherwise = indexPrimArray a1 i == indexPrimArray a2 i && loop (i-1)+ {-# INLINE (==) #-}++-- | Lexicographic ordering. Subject to change between major versions.+--+-- @since 0.6.4.0+instance (Ord a, Prim a) => Ord (PrimArray a) where+ compare a1@(PrimArray ba1#) a2@(PrimArray ba2#)+ | sameByteArray ba1# ba2# = EQ+ | otherwise = loop 0+ where+ sz1 = PB.sizeofByteArray (ByteArray ba1#)+ sz2 = PB.sizeofByteArray (ByteArray ba2#)+ sz = quot (min sz1 sz2) (sizeOf (undefined :: a))+ loop !i+ | i < sz = compare (indexPrimArray a1 i) (indexPrimArray a2 i) <> loop (i+1)+ | otherwise = compare sz1 sz2+ {-# INLINE compare #-}++-- | @since 0.6.4.0+instance Prim a => IsList (PrimArray a) where+ type Item (PrimArray a) = a+ fromList = primArrayFromList+ fromListN = primArrayFromListN+ toList = primArrayToList++-- | @since 0.6.4.0+instance (Show a, Prim a) => Show (PrimArray a) where+ showsPrec p a = showParen (p > 10) $+ showString "fromListN " . shows (sizeofPrimArray a) . showString " "+ . shows (primArrayToList a)++die :: String -> String -> a+die fun problem = error $ "Data.Primitive.PrimArray." ++ fun ++ ": " ++ problem++primArrayFromList :: Prim a => [a] -> PrimArray a+primArrayFromList vs = primArrayFromListN (L.length vs) vs++primArrayFromListN :: forall a. Prim a => Int -> [a] -> PrimArray a+primArrayFromListN len vs = runST run where+ run :: forall s. ST s (PrimArray a)+ run = do+ arr <- newPrimArray len+ let go :: [a] -> Int -> ST s ()+ go [] !ix = if ix == len+ then return ()+ else die "fromListN" "list length less than specified size"+ go (a : as) !ix = if ix < len+ then do+ writePrimArray arr ix a+ go as (ix + 1)+ else die "fromListN" "list length greater than specified size"+ go vs 0+ unsafeFreezePrimArray arr++-- | Convert the primitive array to a list.+{-# INLINE primArrayToList #-}+primArrayToList :: forall a. Prim a => PrimArray a -> [a]+primArrayToList xs = build (\c n -> foldrPrimArray c n xs)++primArrayToByteArray :: PrimArray a -> PB.ByteArray+primArrayToByteArray (PrimArray x) = PB.ByteArray x++byteArrayToPrimArray :: ByteArray -> PrimArray a+byteArrayToPrimArray (PB.ByteArray x) = PrimArray x++#if MIN_VERSION_base(4,9,0)+-- | @since 0.6.4.0+instance Semigroup (PrimArray a) where+ x <> y = byteArrayToPrimArray (primArrayToByteArray x SG.<> primArrayToByteArray y)+ sconcat = byteArrayToPrimArray . SG.sconcat . fmap primArrayToByteArray+ stimes i arr = byteArrayToPrimArray (SG.stimes i (primArrayToByteArray arr))+#endif++-- | @since 0.6.4.0+instance Monoid (PrimArray a) where+ mempty = emptyPrimArray+#if !(MIN_VERSION_base(4,11,0))+ mappend x y = byteArrayToPrimArray (mappend (primArrayToByteArray x) (primArrayToByteArray y))+#endif+ mconcat = byteArrayToPrimArray . mconcat . map primArrayToByteArray++-- | The empty primitive array.+emptyPrimArray :: PrimArray a+{-# NOINLINE emptyPrimArray #-}+emptyPrimArray = runST $ primitive $ \s0# -> case newByteArray# 0# s0# of+ (# s1#, arr# #) -> case unsafeFreezeByteArray# arr# s1# of+ (# s2#, arr'# #) -> (# s2#, PrimArray arr'# #)++-- | Create a new mutable primitive array of the given length. The+-- underlying memory is left uninitialized.+newPrimArray :: forall m a. (PrimMonad m, Prim a) => Int -> m (MutablePrimArray (PrimState m) a)+{-# INLINE newPrimArray #-}+newPrimArray (I# n#)+ = primitive (\s# ->+ case newByteArray# (n# *# sizeOf# (undefined :: a)) s# of+ (# s'#, arr# #) -> (# s'#, MutablePrimArray arr# #)+ )++-- | Resize a mutable primitive array. The new size is given in elements.+--+-- This will either resize the array in-place or, if not possible, allocate the+-- contents into a new, unpinned array and copy the original array\'s contents.+--+-- To avoid undefined behaviour, the original 'MutablePrimArray' shall not be+-- accessed anymore after a 'resizeMutablePrimArray' has been performed.+-- Moreover, no reference to the old one should be kept in order to allow+-- garbage collection of the original 'MutablePrimArray' in case a new+-- 'MutablePrimArray' had to be allocated.+resizeMutablePrimArray :: forall m a. (PrimMonad m, Prim a)+ => MutablePrimArray (PrimState m) a+ -> Int -- ^ new size+ -> m (MutablePrimArray (PrimState m) a)+{-# INLINE resizeMutablePrimArray #-}+resizeMutablePrimArray (MutablePrimArray arr#) (I# n#)+ = primitive (\s# -> case resizeMutableByteArray# arr# (n# *# sizeOf# (undefined :: a)) s# of+ (# s'#, arr'# #) -> (# s'#, MutablePrimArray arr'# #))++-- Although it is possible to shim resizeMutableByteArray for old GHCs, this+-- is not the case with shrinkMutablePrimArray.++-- | Shrink a mutable primitive array. The new size is given in elements.+-- It must be smaller than the old size. The array will be resized in place.+-- This function is only available when compiling with GHC 7.10 or newer.+shrinkMutablePrimArray :: forall m a. (PrimMonad m, Prim a)+ => MutablePrimArray (PrimState m) a+ -> Int -- ^ new size+ -> m ()+{-# INLINE shrinkMutablePrimArray #-}+shrinkMutablePrimArray (MutablePrimArray arr#) (I# n#)+ = primitive_ (shrinkMutableByteArray# arr# (n# *# sizeOf# (undefined :: a)))++readPrimArray :: (Prim a, PrimMonad m) => MutablePrimArray (PrimState m) a -> Int -> m a+{-# INLINE readPrimArray #-}+readPrimArray (MutablePrimArray arr#) (I# i#)+ = primitive (readByteArray# arr# i#)++-- | Write an element to the given index.+writePrimArray ::+ (Prim a, PrimMonad m)+ => MutablePrimArray (PrimState m) a -- ^ array+ -> Int -- ^ index+ -> a -- ^ element+ -> m ()+{-# INLINE writePrimArray #-}+writePrimArray (MutablePrimArray arr#) (I# i#) x+ = primitive_ (writeByteArray# arr# i# x)++-- | Copy part of a mutable array into another mutable array.+-- In the case that the destination and+-- source arrays are the same, the regions may overlap.+copyMutablePrimArray :: forall m a.+ (PrimMonad m, Prim a)+ => MutablePrimArray (PrimState m) a -- ^ destination array+ -> Int -- ^ offset into destination array+ -> MutablePrimArray (PrimState m) a -- ^ source array+ -> Int -- ^ offset into source array+ -> Int -- ^ number of elements to copy+ -> m ()+{-# INLINE copyMutablePrimArray #-}+copyMutablePrimArray (MutablePrimArray dst#) (I# doff#) (MutablePrimArray src#) (I# soff#) (I# n#)+ = primitive_ (copyMutableByteArray#+ src#+ (soff# *# (sizeOf# (undefined :: a)))+ dst#+ (doff# *# (sizeOf# (undefined :: a)))+ (n# *# (sizeOf# (undefined :: a)))+ )++-- | Copy part of an array into another mutable array.+copyPrimArray :: forall m a.+ (PrimMonad m, Prim a)+ => MutablePrimArray (PrimState m) a -- ^ destination array+ -> Int -- ^ offset into destination array+ -> PrimArray a -- ^ source array+ -> Int -- ^ offset into source array+ -> Int -- ^ number of elements to copy+ -> m ()+{-# INLINE copyPrimArray #-}+copyPrimArray (MutablePrimArray dst#) (I# doff#) (PrimArray src#) (I# soff#) (I# n#)+ = primitive_ (copyByteArray#+ src#+ (soff# *# (sizeOf# (undefined :: a)))+ dst#+ (doff# *# (sizeOf# (undefined :: a)))+ (n# *# (sizeOf# (undefined :: a)))+ )++-- | Copy a slice of an immutable primitive array to an address.+-- The offset and length are given in elements of type @a@.+-- This function assumes that the 'Prim' instance of @a@+-- agrees with the 'Storable' instance. This function is only+-- available when building with GHC 7.8 or newer.+copyPrimArrayToPtr :: forall m a. (PrimMonad m, Prim a)+ => Ptr a -- ^ destination pointer+ -> PrimArray a -- ^ source array+ -> Int -- ^ offset into source array+ -> Int -- ^ number of prims to copy+ -> m ()+{-# INLINE copyPrimArrayToPtr #-}+copyPrimArrayToPtr (Ptr addr#) (PrimArray ba#) (I# soff#) (I# n#) =+ primitive (\ s# ->+ let s'# = copyByteArrayToAddr# ba# (soff# *# siz#) addr# (n# *# siz#) s#+ in (# s'#, () #))+ where siz# = sizeOf# (undefined :: a)++-- | Copy a slice of an immutable primitive array to an address.+-- The offset and length are given in elements of type @a@.+-- This function assumes that the 'Prim' instance of @a@+-- agrees with the 'Storable' instance. This function is only+-- available when building with GHC 7.8 or newer.+copyMutablePrimArrayToPtr :: forall m a. (PrimMonad m, Prim a)+ => Ptr a -- ^ destination pointer+ -> MutablePrimArray (PrimState m) a -- ^ source array+ -> Int -- ^ offset into source array+ -> Int -- ^ number of prims to copy+ -> m ()+{-# INLINE copyMutablePrimArrayToPtr #-}+copyMutablePrimArrayToPtr (Ptr addr#) (MutablePrimArray mba#) (I# soff#) (I# n#) =+ primitive (\ s# ->+ let s'# = copyMutableByteArrayToAddr# mba# (soff# *# siz#) addr# (n# *# siz#) s#+ in (# s'#, () #))+ where siz# = sizeOf# (undefined :: a)++-- | Fill a slice of a mutable primitive array with a value.+setPrimArray+ :: (Prim a, PrimMonad m)+ => MutablePrimArray (PrimState m) a -- ^ array to fill+ -> Int -- ^ offset into array+ -> Int -- ^ number of values to fill+ -> a -- ^ value to fill with+ -> m ()+{-# INLINE setPrimArray #-}+setPrimArray (MutablePrimArray dst#) (I# doff#) (I# sz#) x+ = primitive_ (PT.setByteArray# dst# doff# sz# x)++-- | Get the size of a mutable primitive array in elements. Unlike 'sizeofMutablePrimArray',+-- this function ensures sequencing in the presence of resizing.+getSizeofMutablePrimArray :: forall m a. (PrimMonad m, Prim a)+ => MutablePrimArray (PrimState m) a -- ^ array+ -> m Int+{-# INLINE getSizeofMutablePrimArray #-}+#if __GLASGOW_HASKELL__ >= 801+getSizeofMutablePrimArray (MutablePrimArray arr#)+ = primitive (\s# ->+ case getSizeofMutableByteArray# arr# s# of+ (# s'#, sz# #) -> (# s'#, I# (quotInt# sz# (sizeOf# (undefined :: a))) #)+ )+#else+-- On older GHCs, it is not possible to resize a byte array, so+-- this provides behavior consistent with the implementation for+-- newer GHCs.+getSizeofMutablePrimArray arr+ = return (sizeofMutablePrimArray arr)+#endif++-- | Size of the mutable primitive array in elements. This function shall not+-- be used on primitive arrays that are an argument to or a result of+-- 'resizeMutablePrimArray' or 'shrinkMutablePrimArray'.+sizeofMutablePrimArray :: forall s a. Prim a => MutablePrimArray s a -> Int+{-# INLINE sizeofMutablePrimArray #-}+sizeofMutablePrimArray (MutablePrimArray arr#) =+ I# (quotInt# (sizeofMutableByteArray# arr#) (sizeOf# (undefined :: a)))++-- | Check if the two arrays refer to the same memory block.+sameMutablePrimArray :: MutablePrimArray s a -> MutablePrimArray s a -> Bool+{-# INLINE sameMutablePrimArray #-}+sameMutablePrimArray (MutablePrimArray arr#) (MutablePrimArray brr#)+ = isTrue# (sameMutableByteArray# arr# brr#)++-- | Convert a mutable byte array to an immutable one without copying. The+-- array should not be modified after the conversion.+unsafeFreezePrimArray+ :: PrimMonad m => MutablePrimArray (PrimState m) a -> m (PrimArray a)+{-# INLINE unsafeFreezePrimArray #-}+unsafeFreezePrimArray (MutablePrimArray arr#)+ = primitive (\s# -> case unsafeFreezeByteArray# arr# s# of+ (# s'#, arr'# #) -> (# s'#, PrimArray arr'# #))++-- | Convert an immutable array to a mutable one without copying. The+-- original array should not be used after the conversion.+unsafeThawPrimArray+ :: PrimMonad m => PrimArray a -> m (MutablePrimArray (PrimState m) a)+{-# INLINE unsafeThawPrimArray #-}+unsafeThawPrimArray (PrimArray arr#)+ = primitive (\s# -> (# s#, MutablePrimArray (unsafeCoerce# arr#) #))++-- | Read a primitive value from the primitive array.+indexPrimArray :: forall a. Prim a => PrimArray a -> Int -> a+{-# INLINE indexPrimArray #-}+indexPrimArray (PrimArray arr#) (I# i#) = indexByteArray# arr# i#++-- | Get the size, in elements, of the primitive array.+sizeofPrimArray :: forall a. Prim a => PrimArray a -> Int+{-# INLINE sizeofPrimArray #-}+sizeofPrimArray (PrimArray arr#) = I# (quotInt# (sizeofByteArray# arr#) (sizeOf# (undefined :: a)))++-- | Lazy right-associated fold over the elements of a 'PrimArray'.+{-# INLINE foldrPrimArray #-}+foldrPrimArray :: forall a b. Prim a => (a -> b -> b) -> b -> PrimArray a -> b+foldrPrimArray f z arr = go 0+ where+ !sz = sizeofPrimArray arr+ go !i+ | sz > i = f (indexPrimArray arr i) (go (i+1))+ | otherwise = z++-- | Strict right-associated fold over the elements of a 'PrimArray'.+{-# INLINE foldrPrimArray' #-}+foldrPrimArray' :: forall a b. Prim a => (a -> b -> b) -> b -> PrimArray a -> b+foldrPrimArray' f z0 arr = go (sizeofPrimArray arr - 1) z0+ where+ go !i !acc+ | i < 0 = acc+ | otherwise = go (i - 1) (f (indexPrimArray arr i) acc)++-- | Lazy left-associated fold over the elements of a 'PrimArray'.+{-# INLINE foldlPrimArray #-}+foldlPrimArray :: forall a b. Prim a => (b -> a -> b) -> b -> PrimArray a -> b+foldlPrimArray f z arr = go (sizeofPrimArray arr - 1)+ where+ go !i+ | i < 0 = z+ | otherwise = f (go (i - 1)) (indexPrimArray arr i)++-- | Strict left-associated fold over the elements of a 'PrimArray'.+{-# INLINE foldlPrimArray' #-}+foldlPrimArray' :: forall a b. Prim a => (b -> a -> b) -> b -> PrimArray a -> b+foldlPrimArray' f z0 arr = go 0 z0+ where+ !sz = sizeofPrimArray arr+ go !i !acc+ | i < sz = go (i + 1) (f acc (indexPrimArray arr i))+ | otherwise = acc++-- | Strict left-associated fold over the elements of a 'PrimArray'.+{-# INLINE foldlPrimArrayM' #-}+foldlPrimArrayM' :: (Prim a, Monad m) => (b -> a -> m b) -> b -> PrimArray a -> m b+foldlPrimArrayM' f z0 arr = go 0 z0+ where+ !sz = sizeofPrimArray arr+ go !i !acc1+ | i < sz = do+ acc2 <- f acc1 (indexPrimArray arr i)+ go (i + 1) acc2+ | otherwise = return acc1++-- | Traverse a primitive array. The traversal forces the resulting values and+-- writes them to the new primitive array as it performs the monadic effects.+-- Consequently:+--+-- >>> traversePrimArrayP (\x -> print x $> bool x undefined (x == 2)) (fromList [1, 2, 3 :: Int])+-- 1+-- 2+-- *** Exception: Prelude.undefined+--+-- In many situations, 'traversePrimArrayP' can replace 'traversePrimArray',+-- changing the strictness characteristics of the traversal but typically improving+-- the performance. Consider the following short-circuiting traversal:+--+-- > incrPositiveA :: PrimArray Int -> Maybe (PrimArray Int)+-- > incrPositiveA xs = traversePrimArray (\x -> bool Nothing (Just (x + 1)) (x > 0)) xs+--+-- This can be rewritten using 'traversePrimArrayP'. To do this, we must+-- change the traversal context to @MaybeT (ST s)@, which has a 'PrimMonad'+-- instance:+--+-- > incrPositiveB :: PrimArray Int -> Maybe (PrimArray Int)+-- > incrPositiveB xs = runST $ runMaybeT $ traversePrimArrayP+-- > (\x -> bool (MaybeT (return Nothing)) (MaybeT (return (Just (x + 1)))) (x > 0))+-- > xs+--+-- Benchmarks demonstrate that the second implementation runs 150 times+-- faster than the first. It also results in fewer allocations.+{-# INLINE traversePrimArrayP #-}+traversePrimArrayP :: (PrimMonad m, Prim a, Prim b)+ => (a -> m b)+ -> PrimArray a+ -> m (PrimArray b)+traversePrimArrayP f arr = do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ix = if ix < sz+ then do+ b <- f (indexPrimArray arr ix)+ writePrimArray marr ix b+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr++-- | Filter the primitive array, keeping the elements for which the monadic+-- predicate evaluates true.+{-# INLINE filterPrimArrayP #-}+filterPrimArrayP :: (PrimMonad m, Prim a)+ => (a -> m Bool)+ -> PrimArray a+ -> m (PrimArray a)+filterPrimArrayP f arr = do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ixSrc !ixDst = if ixSrc < sz+ then do+ let a = indexPrimArray arr ixSrc+ b <- f a+ if b+ then do+ writePrimArray marr ixDst a+ go (ixSrc + 1) (ixDst + 1)+ else go (ixSrc + 1) ixDst+ else return ixDst+ lenDst <- go 0 0+ marr' <- resizeMutablePrimArray marr lenDst+ unsafeFreezePrimArray marr'++-- | Map over the primitive array, keeping the elements for which the monadic+-- predicate provides a 'Just'.+{-# INLINE mapMaybePrimArrayP #-}+mapMaybePrimArrayP :: (PrimMonad m, Prim a, Prim b)+ => (a -> m (Maybe b))+ -> PrimArray a+ -> m (PrimArray b)+mapMaybePrimArrayP f arr = do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ixSrc !ixDst = if ixSrc < sz+ then do+ let a = indexPrimArray arr ixSrc+ mb <- f a+ case mb of+ Just b -> do+ writePrimArray marr ixDst b+ go (ixSrc + 1) (ixDst + 1)+ Nothing -> go (ixSrc + 1) ixDst+ else return ixDst+ lenDst <- go 0 0+ marr' <- resizeMutablePrimArray marr lenDst+ unsafeFreezePrimArray marr'++-- | Generate a primitive array by evaluating the monadic generator function+-- at each index.+{-# INLINE generatePrimArrayP #-}+generatePrimArrayP :: (PrimMonad m, Prim a)+ => Int -- ^ length+ -> (Int -> m a) -- ^ generator+ -> m (PrimArray a)+generatePrimArrayP sz f = do+ marr <- newPrimArray sz+ let go !ix = if ix < sz+ then do+ b <- f ix+ writePrimArray marr ix b+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr++-- | Execute the monadic action the given number of times and store the+-- results in a primitive array.+{-# INLINE replicatePrimArrayP #-}+replicatePrimArrayP :: (PrimMonad m, Prim a)+ => Int+ -> m a+ -> m (PrimArray a)+replicatePrimArrayP sz f = do+ marr <- newPrimArray sz+ let go !ix = if ix < sz+ then do+ b <- f+ writePrimArray marr ix b+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr+++-- | Map over the elements of a primitive array.+{-# INLINE mapPrimArray #-}+mapPrimArray :: (Prim a, Prim b)+ => (a -> b)+ -> PrimArray a+ -> PrimArray b+mapPrimArray f arr = runST $ do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ix = if ix < sz+ then do+ let b = f (indexPrimArray arr ix)+ writePrimArray marr ix b+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr++-- | Indexed map over the elements of a primitive array.+{-# INLINE imapPrimArray #-}+imapPrimArray :: (Prim a, Prim b)+ => (Int -> a -> b)+ -> PrimArray a+ -> PrimArray b+imapPrimArray f arr = runST $ do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ix = if ix < sz+ then do+ let b = f ix (indexPrimArray arr ix)+ writePrimArray marr ix b+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr++-- | Filter elements of a primitive array according to a predicate.+{-# INLINE filterPrimArray #-}+filterPrimArray :: Prim a+ => (a -> Bool)+ -> PrimArray a+ -> PrimArray a+filterPrimArray p arr = runST $ do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ixSrc !ixDst = if ixSrc < sz+ then do+ let !a = indexPrimArray arr ixSrc+ if p a+ then do+ writePrimArray marr ixDst a+ go (ixSrc + 1) (ixDst + 1)+ else go (ixSrc + 1) ixDst+ else return ixDst+ dstLen <- go 0 0+ marr' <- resizeMutablePrimArray marr dstLen+ unsafeFreezePrimArray marr'++-- | Filter the primitive array, keeping the elements for which the monadic+-- predicate evaluates true.+filterPrimArrayA ::+ (Applicative f, Prim a)+ => (a -> f Bool) -- ^ mapping function+ -> PrimArray a -- ^ primitive array+ -> f (PrimArray a)+filterPrimArrayA f = \ !ary ->+ let+ !len = sizeofPrimArray ary+ go !ixSrc+ | ixSrc == len = pure $ IxSTA $ \ixDst _ -> return ixDst+ | otherwise = let x = indexPrimArray ary ixSrc in+ liftA2+ (\keep (IxSTA m) -> IxSTA $ \ixDst mary -> if keep+ then writePrimArray (MutablePrimArray mary) ixDst x >> m (ixDst + 1) mary+ else m ixDst mary+ )+ (f x)+ (go (ixSrc + 1))+ in if len == 0+ then pure emptyPrimArray+ else runIxSTA len <$> go 0++-- | Map over the primitive array, keeping the elements for which the applicative+-- predicate provides a 'Just'.+mapMaybePrimArrayA ::+ (Applicative f, Prim a, Prim b)+ => (a -> f (Maybe b)) -- ^ mapping function+ -> PrimArray a -- ^ primitive array+ -> f (PrimArray b)+mapMaybePrimArrayA f = \ !ary ->+ let+ !len = sizeofPrimArray ary+ go !ixSrc+ | ixSrc == len = pure $ IxSTA $ \ixDst _ -> return ixDst+ | otherwise = let x = indexPrimArray ary ixSrc in+ liftA2+ (\mb (IxSTA m) -> IxSTA $ \ixDst mary -> case mb of+ Just b -> writePrimArray (MutablePrimArray mary) ixDst b >> m (ixDst + 1) mary+ Nothing -> m ixDst mary+ )+ (f x)+ (go (ixSrc + 1))+ in if len == 0+ then pure emptyPrimArray+ else runIxSTA len <$> go 0++-- | Map over a primitive array, optionally discarding some elements. This+-- has the same behavior as @Data.Maybe.mapMaybe@.+{-# INLINE mapMaybePrimArray #-}+mapMaybePrimArray :: (Prim a, Prim b)+ => (a -> Maybe b)+ -> PrimArray a+ -> PrimArray b+mapMaybePrimArray p arr = runST $ do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ixSrc !ixDst = if ixSrc < sz+ then do+ let !a = indexPrimArray arr ixSrc+ case p a of+ Just b -> do+ writePrimArray marr ixDst b+ go (ixSrc + 1) (ixDst + 1)+ Nothing -> go (ixSrc + 1) ixDst+ else return ixDst+ dstLen <- go 0 0+ marr' <- resizeMutablePrimArray marr dstLen+ unsafeFreezePrimArray marr'+++-- | Traverse a primitive array. The traversal performs all of the applicative+-- effects /before/ forcing the resulting values and writing them to the new+-- primitive array. Consequently:+--+-- >>> traversePrimArray (\x -> print x $> bool x undefined (x == 2)) (fromList [1, 2, 3 :: Int])+-- 1+-- 2+-- 3+-- *** Exception: Prelude.undefined+--+-- The function 'traversePrimArrayP' always outperforms this function, but it+-- requires a 'PrimMonad' constraint, and it forces the values as+-- it performs the effects.+traversePrimArray ::+ (Applicative f, Prim a, Prim b)+ => (a -> f b) -- ^ mapping function+ -> PrimArray a -- ^ primitive array+ -> f (PrimArray b)+traversePrimArray f = \ !ary ->+ let+ !len = sizeofPrimArray ary+ go !i+ | i == len = pure $ STA $ \mary -> unsafeFreezePrimArray (MutablePrimArray mary)+ | x <- indexPrimArray ary i+ = liftA2 (\b (STA m) -> STA $ \mary ->+ writePrimArray (MutablePrimArray mary) i b >> m mary)+ (f x) (go (i + 1))+ in if len == 0+ then pure emptyPrimArray+ else runSTA len <$> go 0++-- | Traverse a primitive array with the index of each element.+itraversePrimArray ::+ (Applicative f, Prim a, Prim b)+ => (Int -> a -> f b)+ -> PrimArray a+ -> f (PrimArray b)+itraversePrimArray f = \ !ary ->+ let+ !len = sizeofPrimArray ary+ go !i+ | i == len = pure $ STA $ \mary -> unsafeFreezePrimArray (MutablePrimArray mary)+ | x <- indexPrimArray ary i+ = liftA2 (\b (STA m) -> STA $ \mary ->+ writePrimArray (MutablePrimArray mary) i b >> m mary)+ (f i x) (go (i + 1))+ in if len == 0+ then pure emptyPrimArray+ else runSTA len <$> go 0++-- | Traverse a primitive array with the indices. The traversal forces the+-- resulting values and writes them to the new primitive array as it performs+-- the monadic effects.+{-# INLINE itraversePrimArrayP #-}+itraversePrimArrayP :: (Prim a, Prim b, PrimMonad m)+ => (Int -> a -> m b)+ -> PrimArray a+ -> m (PrimArray b)+itraversePrimArrayP f arr = do+ let !sz = sizeofPrimArray arr+ marr <- newPrimArray sz+ let go !ix+ | ix < sz = do+ writePrimArray marr ix =<< f ix (indexPrimArray arr ix)+ go (ix + 1)+ | otherwise = return ()+ go 0+ unsafeFreezePrimArray marr++-- | Generate a primitive array.+{-# INLINE generatePrimArray #-}+generatePrimArray :: Prim a+ => Int -- ^ length+ -> (Int -> a) -- ^ element from index+ -> PrimArray a+generatePrimArray len f = runST $ do+ marr <- newPrimArray len+ let go !ix = if ix < len+ then do+ writePrimArray marr ix (f ix)+ go (ix + 1)+ else return ()+ go 0+ unsafeFreezePrimArray marr++-- | Create a primitive array by copying the element the given+-- number of times.+{-# INLINE replicatePrimArray #-}+replicatePrimArray :: Prim a+ => Int -- ^ length+ -> a -- ^ element+ -> PrimArray a+replicatePrimArray len a = runST $ do+ marr <- newPrimArray len+ setPrimArray marr 0 len a+ unsafeFreezePrimArray marr++-- | Generate a primitive array by evaluating the applicative generator+-- function at each index.+{-# INLINE generatePrimArrayA #-}+generatePrimArrayA ::+ (Applicative f, Prim a)+ => Int -- ^ length+ -> (Int -> f a) -- ^ element from index+ -> f (PrimArray a)+generatePrimArrayA len f =+ let+ go !i+ | i == len = pure $ STA $ \mary -> unsafeFreezePrimArray (MutablePrimArray mary)+ | otherwise+ = liftA2 (\b (STA m) -> STA $ \mary ->+ writePrimArray (MutablePrimArray mary) i b >> m mary)+ (f i) (go (i + 1))+ in if len == 0+ then pure emptyPrimArray+ else runSTA len <$> go 0++-- | Execute the applicative action the given number of times and store the+-- results in a vector.+{-# INLINE replicatePrimArrayA #-}+replicatePrimArrayA ::+ (Applicative f, Prim a)+ => Int -- ^ length+ -> f a -- ^ applicative element producer+ -> f (PrimArray a)+replicatePrimArrayA len f =+ let+ go !i+ | i == len = pure $ STA $ \mary -> unsafeFreezePrimArray (MutablePrimArray mary)+ | otherwise+ = liftA2 (\b (STA m) -> STA $ \mary ->+ writePrimArray (MutablePrimArray mary) i b >> m mary)+ f (go (i + 1))+ in if len == 0+ then pure emptyPrimArray+ else runSTA len <$> go 0++-- | Traverse the primitive array, discarding the results. There+-- is no 'PrimMonad' variant of this function since it would not provide+-- any performance benefit.+traversePrimArray_ ::+ (Applicative f, Prim a)+ => (a -> f b)+ -> PrimArray a+ -> f ()+traversePrimArray_ f a = go 0 where+ !sz = sizeofPrimArray a+ go !ix = if ix < sz+ then f (indexPrimArray a ix) *> go (ix + 1)+ else pure ()++-- | Traverse the primitive array with the indices, discarding the results.+-- There is no 'PrimMonad' variant of this function since it would not+-- provide any performance benefit.+itraversePrimArray_ ::+ (Applicative f, Prim a)+ => (Int -> a -> f b)+ -> PrimArray a+ -> f ()+itraversePrimArray_ f a = go 0 where+ !sz = sizeofPrimArray a+ go !ix = if ix < sz+ then f ix (indexPrimArray a ix) *> go (ix + 1)+ else pure ()++newtype IxSTA a = IxSTA {_runIxSTA :: forall s. Int -> MutableByteArray# s -> ST s Int}++runIxSTA :: forall a. Prim a+ => Int -- maximum possible size+ -> IxSTA a+ -> PrimArray a+runIxSTA !szUpper = \ (IxSTA m) -> runST $ do+ ar :: MutablePrimArray s a <- newPrimArray szUpper+ sz <- m 0 (unMutablePrimArray ar)+ ar' <- resizeMutablePrimArray ar sz+ unsafeFreezePrimArray ar'+{-# INLINE runIxSTA #-}++newtype STA a = STA {_runSTA :: forall s. MutableByteArray# s -> ST s (PrimArray a)}++runSTA :: forall a. Prim a => Int -> STA a -> PrimArray a+runSTA !sz = \ (STA m) -> runST $ newPrimArray sz >>= \ (ar :: MutablePrimArray s a) -> m (unMutablePrimArray ar)+{-# INLINE runSTA #-}++unMutablePrimArray :: MutablePrimArray s a -> MutableByteArray# s+unMutablePrimArray (MutablePrimArray m) = m++{- $effectfulMapCreate+The naming conventions adopted in this section are explained in the+documentation of the @Data.Primitive@ module.+-}
src/Streamly/Internal/Data/SVar.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE ConstraintKinds #-}@@ -45,6 +44,7 @@ , setYieldLimit , getInspectMode , setInspectMode+ , recordMaxWorkers , cleanupSVar , cleanupSVarFromWorker@@ -61,13 +61,17 @@ , ChildEvent (..) , AheadHeapEntry (..) , send+ , sendToProducer , sendYield , sendStop+ , sendStopToProducer , enqueueLIFO , enqueueFIFO , enqueueAhead , reEnqueueAhead , pushWorkerPar+ , handleChildException+ , handleFoldException , queueEmptyAhead , dequeueAhead@@ -96,22 +100,28 @@ , postProcessPaced , readOutputQBounded , readOutputQPaced+ , readOutputQBasic , dispatchWorkerPaced , sendFirstWorker , delThread , modifyThread , doFork+ , fork+ , forkManaged , toStreamVar , SVarStats (..) , dumpSVar+ , printSVar+ , withDiagMVar ) where import Control.Concurrent- (ThreadId, myThreadId, threadDelay, throwTo)+ (ThreadId, myThreadId, threadDelay, throwTo, forkIO, killThread) import Control.Concurrent.MVar- (MVar, newEmptyMVar, tryPutMVar, takeMVar, tryTakeMVar, newMVar, tryReadMVar)+ (MVar, newEmptyMVar, tryPutMVar, takeMVar, tryTakeMVar, newMVar,+ tryReadMVar) import Control.Exception (SomeException(..), catch, mask, assert, Exception, catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),@@ -119,7 +129,8 @@ import Control.Monad (when) import Control.Monad.Catch (MonadThrow) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Trans.Control (MonadBaseControl, control, StM)+import Control.Monad.Trans.Control+ (MonadBaseControl, control, StM, liftBaseDiscard) import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier, storeLoadBarrier)@@ -140,11 +151,13 @@ import GHC.Conc (ThreadId(..)) import GHC.Exts import GHC.IO (IO(..))+import System.IO (hPutStrLn, stderr)+import System.Mem.Weak (addFinalizer)+ import Streamly.Internal.Data.Time.Clock (Clock(..), getTime) import Streamly.Internal.Data.Time.Units (AbsTime, NanoSecond64(..), MicroSecond64(..), diffAbsTime64, fromRelTime64, toRelTime64, showNanoSecond64, showRelTime64)-import System.IO (hPutStrLn, stderr) import qualified Data.Heap as H import qualified Data.Set as S@@ -418,6 +431,13 @@ , readOutputQ :: m [ChildEvent a] , postProcess :: m Bool + -- channel to send events from the consumer to the worker. Used to send+ -- exceptions from a fold driver to the fold computation running as a+ -- consumer thread in the concurrent fold cases. Currently only one event+ -- is sent by the fold so we do not really need a queue for it.+ , outputQueueFromConsumer :: IORef ([ChildEvent a], Int)+ , outputDoorBellFromConsumer :: MVar ()+ -- Combined/aggregate parameters -- This is truncated to maxBufferLimit if set to more than that. Otherwise -- potentially each worker may yield one value to the buffer in the worst@@ -857,6 +877,11 @@ <> "---------STATS-----------\n" <> stats +printSVar :: SVar t m a -> String -> IO ()+printSVar sv how = do+ svInfo <- dumpSVar sv+ hPutStrLn stderr $ "\n" <> how <> "\n" <> svInfo+ -- MVar diagnostics has some overhead - around 5% on asyncly null benchmark, we -- can keep it on in production to debug problems quickly if and when they -- happen, but it may result in unexpected output when threads are left hanging@@ -931,6 +956,20 @@ exHandler run (return tid) +{-# INLINABLE fork #-}+fork :: MonadBaseControl IO m => m () -> m ThreadId+fork = liftBaseDiscard forkIO++-- | Fork a thread that is automatically killed as soon as the reference to the+-- returned threadId is garbage collected.+--+{-# INLINABLE forkManaged #-}+forkManaged :: (MonadIO m, MonadBaseControl IO m) => m () -> m ThreadId+forkManaged action = do+ tid <- fork action+ liftIO $ addFinalizer tid (killThread tid)+ return tid+ ------------------------------------------------------------------------------ -- Collecting results from child workers in a streamed fashion ------------------------------------------------------------------------------@@ -1040,13 +1079,12 @@ Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv) (const (fromIntegral n)) --- | This function is used by the producer threads to queue output for the--- consumer thread to consume. Returns whether the queue has more space.-send :: SVar t m a -> ChildEvent a -> IO Int-send sv msg = do- -- XXX can the access to outputQueue and maxBufferLimit be made faster- -- somehow?- oldlen <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->+{-# INLINE sendWithDoorBell #-}+sendWithDoorBell ::+ IORef ([ChildEvent a], Int) -> MVar () -> ChildEvent a -> IO Int+sendWithDoorBell q bell msg = do+ -- XXX can the access to outputQueue be made faster somehow?+ oldlen <- atomicModifyIORefCAS q $ \(es, n) -> ((msg : es, n + 1), n) when (oldlen <= 0) $ do -- The wake up must happen only after the store has finished otherwise@@ -1058,9 +1096,31 @@ -- to read the queue again and find it empty. -- The important point is that the consumer is guaranteed to receive a -- doorbell if something was added to the queue after it empties it.- void $ tryPutMVar (outputDoorBell sv) ()+ void $ tryPutMVar bell () return oldlen +-- | This function is used by the producer threads to queue output for the+-- consumer thread to consume. Returns whether the queue has more space.+send :: SVar t m a -> ChildEvent a -> IO Int+send sv msg = sendWithDoorBell (outputQueue sv) (outputDoorBell sv) msg++-- There is no bound implemented on the buffer, this is assumed to be low+-- traffic.+sendToProducer :: SVar t m a -> ChildEvent a -> IO Int+sendToProducer sv msg = do+ -- In case the producer stream is blocked on pushing to the fold buffer+ -- then wake it up so that it can check for the stop event or exception+ -- being sent to it otherwise we will be deadlocked.+ void $ tryPutMVar (pushBufferMVar sv) ()+ sendWithDoorBell (outputQueueFromConsumer sv)+ (outputDoorBellFromConsumer sv) msg++-- {-# NOINLINE sendStopToProducer #-}+sendStopToProducer :: MonadIO m => SVar t m a -> m ()+sendStopToProducer sv = liftIO $ do+ tid <- myThreadId+ void $ sendToProducer sv (ChildStop tid Nothing)+ workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64)) workerCollectLatency winfo = do (cnt0, t0) <- readIORef (workerLatencyStart winfo)@@ -1488,6 +1548,12 @@ tid <- myThreadId void $ send sv (ChildStop tid (Just e)) +{-# NOINLINE handleFoldException #-}+handleFoldException :: SVar t m a -> SomeException -> IO ()+handleFoldException sv e = do+ tid <- myThreadId+ void $ sendToProducer sv (ChildStop tid (Just e))+ {-# NOINLINE recordMaxWorkers #-} recordMaxWorkers :: MonadIO m => SVar t m a -> m () recordMaxWorkers sv = liftIO $ do@@ -1666,7 +1732,7 @@ -- maxWorkerLatency. -- let- -- How many workers do we need to acheive the required rate?+ -- How many workers do we need to achieve the required rate? -- -- When the workers are IO bound we can increase the throughput by -- increasing the number of workers as long as the IO device has enough@@ -1683,7 +1749,7 @@ -- use that to determine the max rate of workers, and also take the CPU -- bandwidth into account. We can also discover the IO bandwidth if we -- know that we are not CPU bound, then how much steady state rate are- -- we able to acheive. Design tests for CPU bound and IO bound cases.+ -- we able to achieve. Design tests for CPU bound and IO bound cases. -- Calculate how many yields are we ahead or behind to match the exact -- required rate. Based on that we increase or decrease the effective@@ -1999,10 +2065,14 @@ -- Reading from the workers' output queue/buffer ------------------------------------------------------------------------------- +{-# INLINE readOutputQBasic #-}+readOutputQBasic :: IORef ([ChildEvent a], Int) -> IO ([ChildEvent a], Int)+readOutputQBasic q = atomicModifyIORefCAS q $ \x -> (([],0), x)+ {-# INLINE readOutputQRaw #-} readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int) readOutputQRaw sv = do- (list, len) <- atomicModifyIORefCAS (outputQueue sv) $ \x -> (([],0), x)+ (list, len) <- readOutputQBasic (outputQueue sv) when (svarInspectMode sv) $ do let ref = maxOutQSize $ svarStats sv oqLen <- readIORef ref@@ -2196,6 +2266,7 @@ let getSVar sv readOutput postProc = SVar { outputQueue = outQ+ , outputQueueFromConsumer = undefined , remainingWork = yl , maxBufferLimit = getMaxBuffer st , pushBufferSpace = undefined@@ -2204,6 +2275,7 @@ , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st) , yieldRateInfo = rateInfo , outputDoorBell = outQMv+ , outputDoorBellFromConsumer = undefined , readOutputQ = readOutput sv , postProcess = postProc sv , workerThreads = running@@ -2265,7 +2337,9 @@ => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a) getParallelSVar ss st mrun = do outQ <- newIORef ([], 0)+ outQRev <- newIORef ([], 0) outQMv <- newEmptyMVar+ outQMvRev <- newEmptyMVar active <- newIORef 0 running <- newIORef S.empty yl <- case getYieldLimit st of@@ -2289,6 +2363,7 @@ let sv = SVar { outputQueue = outQ+ , outputQueueFromConsumer = outQRev , remainingWork = yl , maxBufferLimit = getMaxBuffer st , pushBufferSpace = remBuf@@ -2298,6 +2373,7 @@ -- Used only for diagnostics , yieldRateInfo = rateInfo , outputDoorBell = outQMv+ , outputDoorBellFromConsumer = outQMvRev , readOutputQ = readOutputQPar sv , postProcess = allThreadsDone sv , workerThreads = running
src/Streamly/Internal/Data/Sink.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_HADDOCK hide #-}- -- | -- Module : Streamly.Internal.Data.Sink -- Copyright : (c) 2019 Composewell Technologies
src/Streamly/Internal/Data/Sink/Types.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_HADDOCK hide #-}- -- | -- Module : Streamly.Internal.Data.Sink.Types -- Copyright : (c) 2019 Composewell Technologies@@ -18,9 +16,9 @@ -- Sink ------------------------------------------------------------------------------ --- | A 'Sink' is a special type of 'Foldl' that does not accumulate any value,+-- | A 'Sink' is a special type of 'Fold' that does not accumulate any value, -- but runs only effects. A 'Sink' has no state to maintain therefore can be a--- bit more efficient than a 'Foldl' with '()' as the state, especially when+-- bit more efficient than a 'Fold' with '()' as the state, especially when -- 'Sink's are composed with other operations. A Sink can be upgraded to a--- 'Foldl', but a 'Foldl' cannot be converted into a Sink.+-- 'Fold', but a 'Fold' cannot be converted into a Sink. data Sink m a = Sink (a -> m ())
+ src/Streamly/Internal/Data/SmallArray.hs view
@@ -0,0 +1,184 @@+-- |+-- Module : Streamly.Internal.Data.SmallArray+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}++#include "inline.hs"++module Streamly.Internal.Data.SmallArray+ (+ -- XXX should it be just Array instead? We should be able to replace one+ -- array type with another easily.+ SmallArray(..)++ , foldl'+ , foldr++ , length++ , writeN++ , toStreamD+ , toStreamDRev++ , toStream+ , toStreamRev+ , read++ , fromListN+ , fromStreamDN+ , fromStreamN++ , streamFold+ , fold+ )+where++import Prelude hiding (foldr, length, read)+import Control.DeepSeq (NFData(..))+import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO, liftIO)+import GHC.IO (unsafePerformIO)+import Data.Functor.Identity (runIdentity)++import Streamly.Internal.Data.SmallArray.Types+import Streamly.Internal.Data.Unfold.Types (Unfold(..))+import Streamly.Internal.Data.Fold.Types (Fold(..))+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)++import qualified Streamly.Internal.Data.Stream.StreamD as D++{-# NOINLINE bottomElement #-}+bottomElement :: a+bottomElement = undefined++{-# INLINE length #-}+length :: SmallArray a -> Int+length arr = sizeofSmallArray arr++{-# INLINE_NORMAL toStreamD #-}+toStreamD :: Monad m => SmallArray a -> D.Stream m a+toStreamD arr = D.Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i == length arr = return D.Stop+ | otherwise =+ return $+ case indexSmallArray## arr i of+ (# x #) -> D.Yield x (i + 1)++{-# INLINE_NORMAL toStreamDRev #-}+toStreamDRev :: Monad m => SmallArray a -> D.Stream m a+toStreamDRev arr = D.Stream step (length arr - 1)+ where+ {-# INLINE_LATE step #-}+ step _ i+ | i < 0 = return D.Stop+ | otherwise =+ return $+ case indexSmallArray## arr i of+ (# x #) -> D.Yield x (i - 1)++{-# INLINE_NORMAL foldl' #-}+foldl' :: (b -> a -> b) -> b -> SmallArray a -> b+foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr++{-# INLINE_NORMAL foldr #-}+foldr :: (a -> b -> b) -> b -> SmallArray a -> b+foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr++-- | @writeN n@ folds a maximum of @n@ elements from the input stream to an+-- 'SmallArray'.+--+-- Since we are folding to a 'SmallArray' @n@ should be <= 128, for larger number+-- of elements use an 'Array' from either "Streamly.Data.Array" or "Streamly.Memory.Array".+{-# INLINE_NORMAL writeN #-}+writeN :: MonadIO m => Int -> Fold m a (SmallArray a)+writeN limit = Fold step initial extract+ where+ initial = do+ marr <- liftIO $ newSmallArray limit bottomElement+ return (marr, 0)+ step (marr, i) x+ | i == limit = return (marr, i)+ | otherwise = do+ liftIO $ writeSmallArray marr i x+ return (marr, i + 1)+ extract (marr, len) = liftIO $ freezeSmallArray marr 0 len++{-# INLINE_NORMAL fromStreamDN #-}+fromStreamDN :: MonadIO m => Int -> D.Stream m a -> m (SmallArray a)+fromStreamDN limit str = do+ marr <- liftIO $ newSmallArray (max limit 0) bottomElement+ i <-+ D.foldlM'+ (\i x -> i `seq` (liftIO $ writeSmallArray marr i x) >> return (i + 1))+ 0 $+ D.take limit str+ liftIO $ freezeSmallArray marr 0 i++-- | Create a 'SmallArray' from the first @n@ elements of a list. The+-- array may hold less than @n@ elements if the length of the list <=+-- @n@.+--+-- It is recommended to use a value of @n@ <= 128. For larger sized+-- arrays, use an 'Array' from "Streamly.Data.Array" or+-- "Streamly.Memory.Array"+{-# INLINABLE fromListN #-}+fromListN :: Int -> [a] -> SmallArray a+fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs++instance NFData a => NFData (SmallArray a) where+ {-# INLINE rnf #-}+ rnf = foldl' (\_ x -> rnf x) ()++-- | Create a 'SmallArray' from the first @n@ elements of a stream. The+-- array is allocated to size @n@, if the stream terminates before @n@+-- elements then the array may hold less than @n@ elements.+--+-- For optimal performance use this with @n@ <= 128.+{-# INLINE fromStreamN #-}+fromStreamN :: MonadIO m => Int -> SerialT m a -> m (SmallArray a)+fromStreamN n m = do+ when (n < 0) $ error "fromStreamN: negative write count specified"+ fromStreamDN n $ D.toStreamD m++{-# INLINE_EARLY toStream #-}+toStream :: (Monad m, IsStream t) => SmallArray a -> t m a+toStream = D.fromStreamD . toStreamD++{-# INLINE_EARLY toStreamRev #-}+toStreamRev :: (Monad m, IsStream t) => SmallArray a -> t m a+toStreamRev = D.fromStreamD . toStreamDRev++{-# INLINE fold #-}+fold :: Monad m => Fold m a b -> SmallArray a -> m b+fold f arr = D.runFold f (toStreamD arr)++{-# INLINE streamFold #-}+streamFold :: Monad m => (SerialT m a -> m b) -> SmallArray a -> m b+streamFold f arr = f (toStream arr)++{-# INLINE_NORMAL read #-}+read :: Monad m => Unfold m (SmallArray a) a+read = Unfold step inject+ where+ inject arr = return (arr, 0)+ step (arr, i)+ | i == length arr = return D.Stop+ | otherwise =+ return $+ case indexSmallArray## arr i of+ (# x #) -> D.Yield x (arr, i + 1)
+ src/Streamly/Internal/Data/SmallArray/Types.hs view
@@ -0,0 +1,834 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE BangPatterns #-}++-- |+-- Module : Data.Primitive.SmallArray+-- Copyright: (c) 2015 Dan Doel+-- License: BSD3+--+-- Maintainer : streamly@composewell.com+-- Portability: non-portable+--+-- Small arrays are boxed (im)mutable arrays.+--+-- The underlying structure of the 'Array' type contains a card table, allowing+-- segments of the array to be marked as having been mutated. This allows the+-- garbage collector to only re-traverse segments of the array that have been+-- marked during certain phases, rather than having to traverse the entire+-- array.+--+-- 'SmallArray' lacks this table. This means that it takes up less memory and+-- has slightly faster writes. It is also more efficient during garbage+-- collection so long as the card table would have a single entry covering the+-- entire array. These advantages make them suitable for use as arrays that are+-- known to be small.+--+-- The card size is 128, so for uses much larger than that, 'Array' would likely+-- be superior.+--+-- The underlying type, 'SmallArray#', was introduced in GHC 7.10, so prior to+-- that version, this module simply implements small arrays as 'Array'.++module Streamly.Internal.Data.SmallArray.Types+ ( SmallArray(..)+ , SmallMutableArray(..)+ , newSmallArray+ , readSmallArray+ , writeSmallArray+ , copySmallArray+ , copySmallMutableArray+ , indexSmallArray+ , indexSmallArrayM+ , indexSmallArray##+ , cloneSmallArray+ , cloneSmallMutableArray+ , freezeSmallArray+ , unsafeFreezeSmallArray+ , thawSmallArray+ , runSmallArray+ , unsafeThawSmallArray+ , sizeofSmallArray+ , sizeofSmallMutableArray+ , smallArrayFromList+ , smallArrayFromListN+ , mapSmallArray'+ , traverseSmallArrayP+ ) where++import GHC.Exts hiding (toList)+import qualified GHC.Exts++import Control.Applicative+import Control.Monad+#if MIN_VERSION_base(4,9,0)+import qualified Control.Monad.Fail as Fail+#endif+import Control.Monad.Fix+import Control.Monad.Primitive+import Control.Monad.ST+import Control.Monad.Zip+import Data.Data+import Data.Foldable as Foldable+import Data.Functor.Identity+#if !(MIN_VERSION_base(4,10,0))+import Data.Monoid+#endif+#if MIN_VERSION_base(4,9,0)+import qualified GHC.ST as GHCST+import qualified Data.Semigroup as Sem+#endif+import Text.ParserCombinators.ReadP++#if MIN_VERSION_base(4,9,0) && !MIN_VERSION_base(4,10,0)+import GHC.Base (runRW#)+#endif++#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)+import Data.Functor.Classes (Eq1(..),Ord1(..),Show1(..),Read1(..))+#endif++data SmallArray a = SmallArray (SmallArray# a)+ deriving Typeable++data SmallMutableArray s a = SmallMutableArray (SmallMutableArray# s a)+ deriving Typeable++-- | Create a new small mutable array.+newSmallArray+ :: PrimMonad m+ => Int -- ^ size+ -> a -- ^ initial contents+ -> m (SmallMutableArray (PrimState m) a)+newSmallArray (I# i#) x = primitive $ \s ->+ case newSmallArray# i# x s of+ (# s', sma# #) -> (# s', SmallMutableArray sma# #)+{-# INLINE newSmallArray #-}++-- | Read the element at a given index in a mutable array.+readSmallArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ array+ -> Int -- ^ index+ -> m a+readSmallArray (SmallMutableArray sma#) (I# i#) =+ primitive $ readSmallArray# sma# i#+{-# INLINE readSmallArray #-}++-- | Write an element at the given idex in a mutable array.+writeSmallArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ array+ -> Int -- ^ index+ -> a -- ^ new element+ -> m ()+writeSmallArray (SmallMutableArray sma#) (I# i#) x =+ primitive_ $ writeSmallArray# sma# i# x+{-# INLINE writeSmallArray #-}++-- | Look up an element in an immutable array.+--+-- The purpose of returning a result using a monad is to allow the caller to+-- avoid retaining references to the array. Evaluating the return value will+-- cause the array lookup to be performed, even though it may not require the+-- element of the array to be evaluated (which could throw an exception). For+-- instance:+--+-- > data Box a = Box a+-- > ...+-- >+-- > f sa = case indexSmallArrayM sa 0 of+-- > Box x -> ...+--+-- 'x' is not a closure that references 'sa' as it would be if we instead+-- wrote:+--+-- > let x = indexSmallArray sa 0+--+-- And does not prevent 'sa' from being garbage collected.+--+-- Note that 'Identity' is not adequate for this use, as it is a newtype, and+-- cannot be evaluated without evaluating the element.+indexSmallArrayM+ :: Monad m+ => SmallArray a -- ^ array+ -> Int -- ^ index+ -> m a+indexSmallArrayM (SmallArray sa#) (I# i#) =+ case indexSmallArray# sa# i# of+ (# x #) -> pure x+{-# INLINE indexSmallArrayM #-}++-- | Look up an element in an immutable array.+indexSmallArray+ :: SmallArray a -- ^ array+ -> Int -- ^ index+ -> a+indexSmallArray sa i = runIdentity $ indexSmallArrayM sa i+{-# INLINE indexSmallArray #-}++-- | Read a value from the immutable array at the given index, returning+-- the result in an unboxed unary tuple. This is currently used to implement+-- folds.+indexSmallArray## :: SmallArray a -> Int -> (# a #)+indexSmallArray## (SmallArray ary) (I# i) = indexSmallArray# ary i+{-# INLINE indexSmallArray## #-}++-- | Create a copy of a slice of an immutable array.+cloneSmallArray+ :: SmallArray a -- ^ source+ -> Int -- ^ offset+ -> Int -- ^ length+ -> SmallArray a+cloneSmallArray (SmallArray sa#) (I# i#) (I# j#) =+ SmallArray (cloneSmallArray# sa# i# j#)+{-# INLINE cloneSmallArray #-}++-- | Create a copy of a slice of a mutable array.+cloneSmallMutableArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ source+ -> Int -- ^ offset+ -> Int -- ^ length+ -> m (SmallMutableArray (PrimState m) a)+cloneSmallMutableArray (SmallMutableArray sma#) (I# o#) (I# l#) =+ primitive $ \s -> case cloneSmallMutableArray# sma# o# l# s of+ (# s', smb# #) -> (# s', SmallMutableArray smb# #)+{-# INLINE cloneSmallMutableArray #-}++-- | Create an immutable array corresponding to a slice of a mutable array.+--+-- This operation copies the portion of the array to be frozen.+freezeSmallArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ source+ -> Int -- ^ offset+ -> Int -- ^ length+ -> m (SmallArray a)+freezeSmallArray (SmallMutableArray sma#) (I# i#) (I# j#) =+ primitive $ \s -> case freezeSmallArray# sma# i# j# s of+ (# s', sa# #) -> (# s', SmallArray sa# #)+{-# INLINE freezeSmallArray #-}++-- | Render a mutable array immutable.+--+-- This operation performs no copying, so care must be taken not to modify the+-- input array after freezing.+unsafeFreezeSmallArray+ :: PrimMonad m => SmallMutableArray (PrimState m) a -> m (SmallArray a)+unsafeFreezeSmallArray (SmallMutableArray sma#) =+ primitive $ \s -> case unsafeFreezeSmallArray# sma# s of+ (# s', sa# #) -> (# s', SmallArray sa# #)+{-# INLINE unsafeFreezeSmallArray #-}++-- | Create a mutable array corresponding to a slice of an immutable array.+--+-- This operation copies the portion of the array to be thawed.+thawSmallArray+ :: PrimMonad m+ => SmallArray a -- ^ source+ -> Int -- ^ offset+ -> Int -- ^ length+ -> m (SmallMutableArray (PrimState m) a)+thawSmallArray (SmallArray sa#) (I# o#) (I# l#) =+ primitive $ \s -> case thawSmallArray# sa# o# l# s of+ (# s', sma# #) -> (# s', SmallMutableArray sma# #)+{-# INLINE thawSmallArray #-}++-- | Render an immutable array mutable.+--+-- This operation performs no copying, so care must be taken with its use.+unsafeThawSmallArray+ :: PrimMonad m => SmallArray a -> m (SmallMutableArray (PrimState m) a)+unsafeThawSmallArray (SmallArray sa#) =+ primitive $ \s -> case unsafeThawSmallArray# sa# s of+ (# s', sma# #) -> (# s', SmallMutableArray sma# #)+{-# INLINE unsafeThawSmallArray #-}++-- | Copy a slice of an immutable array into a mutable array.+copySmallArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ destination+ -> Int -- ^ destination offset+ -> SmallArray a -- ^ source+ -> Int -- ^ source offset+ -> Int -- ^ length+ -> m ()+copySmallArray+ (SmallMutableArray dst#) (I# do#) (SmallArray src#) (I# so#) (I# l#) =+ primitive_ $ copySmallArray# src# so# dst# do# l#+{-# INLINE copySmallArray #-}++-- | Copy a slice of one mutable array into another.+copySmallMutableArray+ :: PrimMonad m+ => SmallMutableArray (PrimState m) a -- ^ destination+ -> Int -- ^ destination offset+ -> SmallMutableArray (PrimState m) a -- ^ source+ -> Int -- ^ source offset+ -> Int -- ^ length+ -> m ()+copySmallMutableArray+ (SmallMutableArray dst#) (I# do#)+ (SmallMutableArray src#) (I# so#)+ (I# l#) =+ primitive_ $ copySmallMutableArray# src# so# dst# do# l#+{-# INLINE copySmallMutableArray #-}++sizeofSmallArray :: SmallArray a -> Int+sizeofSmallArray (SmallArray sa#) = I# (sizeofSmallArray# sa#)+{-# INLINE sizeofSmallArray #-}++sizeofSmallMutableArray :: SmallMutableArray s a -> Int+sizeofSmallMutableArray (SmallMutableArray sa#) =+ I# (sizeofSmallMutableArray# sa#)+{-# INLINE sizeofSmallMutableArray #-}++-- | This is the fastest, most straightforward way to traverse+-- an array, but it only works correctly with a sufficiently+-- "affine" 'PrimMonad' instance. In particular, it must only produce+-- *one* result array. 'Control.Monad.Trans.List.ListT'-transformed+-- monads, for example, will not work right at all.+traverseSmallArrayP+ :: PrimMonad m+ => (a -> m b)+ -> SmallArray a+ -> m (SmallArray b)+traverseSmallArrayP f = \ !ary ->+ let+ !sz = sizeofSmallArray ary+ go !i !mary+ | i == sz+ = unsafeFreezeSmallArray mary+ | otherwise+ = do+ a <- indexSmallArrayM ary i+ b <- f a+ writeSmallArray mary i b+ go (i + 1) mary+ in do+ mary <- newSmallArray sz badTraverseValue+ go 0 mary+{-# INLINE traverseSmallArrayP #-}++-- | Strict map over the elements of the array.+mapSmallArray' :: (a -> b) -> SmallArray a -> SmallArray b+mapSmallArray' f sa = createSmallArray (length sa) (die "mapSmallArray'" "impossible") $ \smb ->+ fix ? 0 $ \go i ->+ when (i < length sa) $ do+ x <- indexSmallArrayM sa i+ let !y = f x+ writeSmallArray smb i y *> go (i+1)+{-# INLINE mapSmallArray' #-}++#if !MIN_VERSION_base(4,9,0)+runSmallArray+ :: (forall s. ST s (SmallMutableArray s a))+ -> SmallArray a+runSmallArray m = runST $ m >>= unsafeFreezeSmallArray++#else+-- This low-level business is designed to work with GHC's worker-wrapper+-- transformation. A lot of the time, we don't actually need an Array+-- constructor. By putting it on the outside, and being careful about+-- how we special-case the empty array, we can make GHC smarter about this.+-- The only downside is that separately created 0-length arrays won't share+-- their Array constructors, although they'll share their underlying+-- Array#s.+runSmallArray+ :: (forall s. ST s (SmallMutableArray s a))+ -> SmallArray a+runSmallArray m = SmallArray (runSmallArray# m)++runSmallArray#+ :: (forall s. ST s (SmallMutableArray s a))+ -> SmallArray# a+runSmallArray# m = case runRW# $ \s ->+ case unST m s of { (# s', SmallMutableArray mary# #) ->+ unsafeFreezeSmallArray# mary# s'} of (# _, ary# #) -> ary#++unST :: ST s a -> State# s -> (# State# s, a #)+unST (GHCST.ST f) = f++#endif++-- See the comment on runSmallArray for why we use emptySmallArray#.+createSmallArray+ :: Int+ -> a+ -> (forall s. SmallMutableArray s a -> ST s ())+ -> SmallArray a+createSmallArray 0 _ _ = SmallArray (emptySmallArray# (# #))+createSmallArray n x f = runSmallArray $ do+ mary <- newSmallArray n x+ f mary+ pure mary++emptySmallArray# :: (# #) -> SmallArray# a+emptySmallArray# _ = case emptySmallArray of SmallArray ar -> ar+{-# NOINLINE emptySmallArray# #-}++die :: String -> String -> a+die fun problem = error $ "Data.Primitive.SmallArray." ++ fun ++ ": " ++ problem++emptySmallArray :: SmallArray a+emptySmallArray =+ runST $ newSmallArray 0 (die "emptySmallArray" "impossible")+ >>= unsafeFreezeSmallArray+{-# NOINLINE emptySmallArray #-}+++infixl 1 ?+(?) :: (a -> b -> c) -> (b -> a -> c)+(?) = flip+{-# INLINE (?) #-}++noOp :: a -> ST s ()+noOp = const $ pure ()++smallArrayLiftEq :: (a -> b -> Bool) -> SmallArray a -> SmallArray b -> Bool+smallArrayLiftEq p sa1 sa2 = length sa1 == length sa2 && loop (length sa1 - 1)+ where+ loop i+ | i < 0+ = True+ | (# x #) <- indexSmallArray## sa1 i+ , (# y #) <- indexSmallArray## sa2 i+ = p x y && loop (i-1)++#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)+-- | @since 0.6.4.0+instance Eq1 SmallArray where+#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)+ liftEq = smallArrayLiftEq+#else+ eq1 = smallArrayLiftEq (==)+#endif+#endif++instance Eq a => Eq (SmallArray a) where+ sa1 == sa2 = smallArrayLiftEq (==) sa1 sa2++instance Eq (SmallMutableArray s a) where+ SmallMutableArray sma1# == SmallMutableArray sma2# =+ isTrue# (sameSmallMutableArray# sma1# sma2#)++smallArrayLiftCompare :: (a -> b -> Ordering) -> SmallArray a -> SmallArray b -> Ordering+smallArrayLiftCompare elemCompare a1 a2 = loop 0+ where+ mn = length a1 `min` length a2+ loop i+ | i < mn+ , (# x1 #) <- indexSmallArray## a1 i+ , (# x2 #) <- indexSmallArray## a2 i+ = elemCompare x1 x2 `mappend` loop (i+1)+ | otherwise = compare (length a1) (length a2)++#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)+-- | @since 0.6.4.0+instance Ord1 SmallArray where+#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)+ liftCompare = smallArrayLiftCompare+#else+ compare1 = smallArrayLiftCompare compare+#endif+#endif++-- | Lexicographic ordering. Subject to change between major versions.+instance Ord a => Ord (SmallArray a) where+ compare sa1 sa2 = smallArrayLiftCompare compare sa1 sa2++instance Foldable SmallArray where+ -- Note: we perform the array lookups eagerly so we won't+ -- create thunks to perform lookups even if GHC can't see+ -- that the folding function is strict.+ foldr f = \z !ary ->+ let+ !sz = sizeofSmallArray ary+ go i+ | i == sz = z+ | (# x #) <- indexSmallArray## ary i+ = f x (go (i+1))+ in go 0+ {-# INLINE foldr #-}+ foldl f = \z !ary ->+ let+ go i+ | i < 0 = z+ | (# x #) <- indexSmallArray## ary i+ = f (go (i-1)) x+ in go (sizeofSmallArray ary - 1)+ {-# INLINE foldl #-}+ foldr1 f = \ !ary ->+ let+ !sz = sizeofSmallArray ary - 1+ go i =+ case indexSmallArray## ary i of+ (# x #) | i == sz -> x+ | otherwise -> f x (go (i+1))+ in if sz < 0+ then die "foldr1" "Empty SmallArray"+ else go 0+ {-# INLINE foldr1 #-}+ foldl1 f = \ !ary ->+ let+ !sz = sizeofSmallArray ary - 1+ go i =+ case indexSmallArray## ary i of+ (# x #) | i == 0 -> x+ | otherwise -> f (go (i - 1)) x+ in if sz < 0+ then die "foldl1" "Empty SmallArray"+ else go sz+ {-# INLINE foldl1 #-}+ foldr' f = \z !ary ->+ let+ go i !acc+ | i == -1 = acc+ | (# x #) <- indexSmallArray## ary i+ = go (i-1) (f x acc)+ in go (sizeofSmallArray ary - 1) z+ {-# INLINE foldr' #-}+ foldl' f = \z !ary ->+ let+ !sz = sizeofSmallArray ary+ go i !acc+ | i == sz = acc+ | (# x #) <- indexSmallArray## ary i+ = go (i+1) (f acc x)+ in go 0 z+ {-# INLINE foldl' #-}+ null a = sizeofSmallArray a == 0+ {-# INLINE null #-}+ length = sizeofSmallArray+ {-# INLINE length #-}+ maximum ary | sz == 0 = die "maximum" "Empty SmallArray"+ | (# frst #) <- indexSmallArray## ary 0+ = go 1 frst+ where+ sz = sizeofSmallArray ary+ go i !e+ | i == sz = e+ | (# x #) <- indexSmallArray## ary i+ = go (i+1) (max e x)+ {-# INLINE maximum #-}+ minimum ary | sz == 0 = die "minimum" "Empty SmallArray"+ | (# frst #) <- indexSmallArray## ary 0+ = go 1 frst+ where sz = sizeofSmallArray ary+ go i !e+ | i == sz = e+ | (# x #) <- indexSmallArray## ary i+ = go (i+1) (min e x)+ {-# INLINE minimum #-}+ sum = foldl' (+) 0+ {-# INLINE sum #-}+ product = foldl' (*) 1+ {-# INLINE product #-}++newtype STA a = STA {_runSTA :: forall s. SmallMutableArray# s a -> ST s (SmallArray a)}++runSTA :: Int -> STA a -> SmallArray a+runSTA !sz = \ (STA m) -> runST $ newSmallArray_ sz >>=+ \ (SmallMutableArray ar#) -> m ar#+{-# INLINE runSTA #-}++newSmallArray_ :: Int -> ST s (SmallMutableArray s a)+newSmallArray_ !n = newSmallArray n badTraverseValue++badTraverseValue :: a+badTraverseValue = die "traverse" "bad indexing"+{-# NOINLINE badTraverseValue #-}++instance Traversable SmallArray where+ traverse f = traverseSmallArray f+ {-# INLINE traverse #-}++traverseSmallArray+ :: Applicative f+ => (a -> f b) -> SmallArray a -> f (SmallArray b)+traverseSmallArray f = \ !ary ->+ let+ !len = sizeofSmallArray ary+ go !i+ | i == len+ = pure $ STA $ \mary -> unsafeFreezeSmallArray (SmallMutableArray mary)+ | (# x #) <- indexSmallArray## ary i+ = liftA2 (\b (STA m) -> STA $ \mary ->+ writeSmallArray (SmallMutableArray mary) i b >> m mary)+ (f x) (go (i + 1))+ in if len == 0+ then pure emptySmallArray+ else runSTA len <$> go 0+{-# INLINE [1] traverseSmallArray #-}++{-# RULES+"traverse/ST" forall (f :: a -> ST s b). traverseSmallArray f = traverseSmallArrayP f+"traverse/IO" forall (f :: a -> IO b). traverseSmallArray f = traverseSmallArrayP f+"traverse/Id" forall (f :: a -> Identity b). traverseSmallArray f =+ (coerce :: (SmallArray a -> SmallArray (Identity b))+ -> SmallArray a -> Identity (SmallArray b)) (fmap f)+ #-}+++instance Functor SmallArray where+ fmap f sa = createSmallArray (length sa) (die "fmap" "impossible") $ \smb ->+ fix ? 0 $ \go i ->+ when (i < length sa) $ do+ x <- indexSmallArrayM sa i+ writeSmallArray smb i (f x) *> go (i+1)+ {-# INLINE fmap #-}++ x <$ sa = createSmallArray (length sa) x noOp++instance Applicative SmallArray where+ pure x = createSmallArray 1 x noOp++ sa *> sb = createSmallArray (la*lb) (die "*>" "impossible") $ \smb ->+ fix ? 0 $ \go i ->+ when (i < la) $+ copySmallArray smb 0 sb 0 lb *> go (i+1)+ where+ la = length sa ; lb = length sb++ a <* b = createSmallArray (sza*szb) (die "<*" "impossible") $ \ma ->+ let fill off i e = when (i < szb) $+ writeSmallArray ma (off+i) e >> fill off (i+1) e+ go i = when (i < sza) $ do+ x <- indexSmallArrayM a i+ fill (i*szb) 0 x+ go (i+1)+ in go 0+ where sza = sizeofSmallArray a ; szb = sizeofSmallArray b++ ab <*> a = createSmallArray (szab*sza) (die "<*>" "impossible") $ \mb ->+ let go1 i = when (i < szab) $+ do+ f <- indexSmallArrayM ab i+ go2 (i*sza) f 0+ go1 (i+1)+ go2 off f j = when (j < sza) $+ do+ x <- indexSmallArrayM a j+ writeSmallArray mb (off + j) (f x)+ go2 off f (j + 1)+ in go1 0+ where szab = sizeofSmallArray ab ; sza = sizeofSmallArray a++instance Alternative SmallArray where+ empty = emptySmallArray++ sl <|> sr =+ createSmallArray (length sl + length sr) (die "<|>" "impossible") $ \sma ->+ copySmallArray sma 0 sl 0 (length sl)+ *> copySmallArray sma (length sl) sr 0 (length sr)++ many sa | null sa = pure []+ | otherwise = die "many" "infinite arrays are not well defined"++ some sa | null sa = emptySmallArray+ | otherwise = die "some" "infinite arrays are not well defined"++data ArrayStack a+ = PushArray !(SmallArray a) !(ArrayStack a)+ | EmptyStack+-- TODO: This isn't terribly efficient. It would be better to wrap+-- ArrayStack with a type like+--+-- data NES s a = NES !Int !(SmallMutableArray s a) !(ArrayStack a)+--+-- We'd copy incoming arrays into the mutable array until we would+-- overflow it. Then we'd freeze it, push it on the stack, and continue.+-- Any sufficiently large incoming arrays would go straight on the stack.+-- Such a scheme would make the stack much more compact in the case+-- of many small arrays.++instance Monad SmallArray where+ return = pure+ (>>) = (*>)++ sa >>= f = collect 0 EmptyStack (la-1)+ where+ la = length sa+ collect sz stk i+ | i < 0 = createSmallArray sz (die ">>=" "impossible") $ fill 0 stk+ | (# x #) <- indexSmallArray## sa i+ , let sb = f x+ lsb = length sb+ -- If we don't perform this check, we could end up allocating+ -- a stack full of empty arrays if someone is filtering most+ -- things out. So we refrain from pushing empty arrays.+ = if lsb == 0+ then collect sz stk (i-1)+ else collect (sz + lsb) (PushArray sb stk) (i-1)++ fill _ EmptyStack _ = return ()+ fill off (PushArray sb sbs) smb =+ copySmallArray smb off sb 0 (length sb)+ *> fill (off + length sb) sbs smb++#if !(MIN_VERSION_base(4,13,0)) && MIN_VERSION_base(4,9,0)+ fail = Fail.fail+#endif++#if MIN_VERSION_base(4,9,0)+instance Fail.MonadFail SmallArray where+ fail _ = emptySmallArray+#endif++instance MonadPlus SmallArray where+ mzero = empty+ mplus = (<|>)++zipW :: String -> (a -> b -> c) -> SmallArray a -> SmallArray b -> SmallArray c+zipW nm = \f sa sb -> let mn = length sa `min` length sb in+ createSmallArray mn (die nm "impossible") $ \mc ->+ fix ? 0 $ \go i -> when (i < mn) $ do+ x <- indexSmallArrayM sa i+ y <- indexSmallArrayM sb i+ writeSmallArray mc i (f x y)+ go (i+1)+{-# INLINE zipW #-}++instance MonadZip SmallArray where+ mzip = zipW "mzip" (,)+ mzipWith = zipW "mzipWith"+ {-# INLINE mzipWith #-}+ munzip sab = runST $ do+ let sz = length sab+ sma <- newSmallArray sz $ die "munzip" "impossible"+ smb <- newSmallArray sz $ die "munzip" "impossible"+ fix ? 0 $ \go i ->+ when (i < sz) $ case indexSmallArray sab i of+ (x, y) -> do writeSmallArray sma i x+ writeSmallArray smb i y+ go $ i+1+ (,) <$> unsafeFreezeSmallArray sma+ <*> unsafeFreezeSmallArray smb++instance MonadFix SmallArray where+ mfix f = createSmallArray (sizeofSmallArray (f err))+ (die "mfix" "impossible") $ flip fix 0 $+ \r !i !mary -> when (i < sz) $ do+ writeSmallArray mary i (fix (\xi -> f xi `indexSmallArray` i))+ r (i + 1) mary+ where+ sz = sizeofSmallArray (f err)+ err = error "mfix for Data.Primitive.SmallArray applied to strict function."++#if MIN_VERSION_base(4,9,0)+-- | @since 0.6.3.0+instance Sem.Semigroup (SmallArray a) where+ (<>) = (<|>)+ sconcat = mconcat . toList+#endif++instance Monoid (SmallArray a) where+ mempty = empty+#if !(MIN_VERSION_base(4,11,0))+ mappend = (<|>)+#endif+ mconcat l = createSmallArray n (die "mconcat" "impossible") $ \ma ->+ let go !_ [ ] = return ()+ go off (a:as) =+ copySmallArray ma off a 0 (sizeofSmallArray a) >> go (off + sizeofSmallArray a) as+ in go 0 l+ where n = sum . fmap length $ l++instance IsList (SmallArray a) where+ type Item (SmallArray a) = a+ fromListN = smallArrayFromListN+ fromList = smallArrayFromList+ toList = Foldable.toList++smallArrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> SmallArray a -> ShowS+smallArrayLiftShowsPrec elemShowsPrec elemListShowsPrec p sa = showParen (p > 10) $+ showString "fromListN " . shows (length sa) . showString " "+ . listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList sa)++-- this need to be included for older ghcs+listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS+listLiftShowsPrec _ sl _ = sl++instance Show a => Show (SmallArray a) where+ showsPrec p sa = smallArrayLiftShowsPrec showsPrec showList p sa++#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)+-- | @since 0.6.4.0+instance Show1 SmallArray where+#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)+ liftShowsPrec = smallArrayLiftShowsPrec+#else+ showsPrec1 = smallArrayLiftShowsPrec showsPrec showList+#endif+#endif++smallArrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (SmallArray a)+smallArrayLiftReadsPrec _ listReadsPrec p = readParen (p > 10) . readP_to_S $ do+ () <$ string "fromListN"+ skipSpaces+ n <- readS_to_P reads+ skipSpaces+ l <- readS_to_P listReadsPrec+ return $ smallArrayFromListN n l++instance Read a => Read (SmallArray a) where+ readsPrec = smallArrayLiftReadsPrec readsPrec readList++#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)+-- | @since 0.6.4.0+instance Read1 SmallArray where+#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)+ liftReadsPrec = smallArrayLiftReadsPrec+#else+ readsPrec1 = smallArrayLiftReadsPrec readsPrec readList+#endif+#endif++++smallArrayDataType :: DataType+smallArrayDataType =+ mkDataType "Data.Primitive.SmallArray.SmallArray" [fromListConstr]++fromListConstr :: Constr+fromListConstr = mkConstr smallArrayDataType "fromList" [] Prefix++instance Data a => Data (SmallArray a) where+ toConstr _ = fromListConstr+ dataTypeOf _ = smallArrayDataType+ gunfold k z c = case constrIndex c of+ 1 -> k (z fromList)+ _ -> die "gunfold" "SmallArray"+ gfoldl f z m = z fromList `f` toList m++instance (Typeable s, Typeable a) => Data (SmallMutableArray s a) where+ toConstr _ = die "toConstr" "SmallMutableArray"+ gunfold _ _ = die "gunfold" "SmallMutableArray"+ dataTypeOf _ = mkNoRepType "Data.Primitive.SmallArray.SmallMutableArray"++-- | Create a 'SmallArray' from a list of a known length. If the length+-- of the list does not match the given length, this throws an exception.+smallArrayFromListN :: Int -> [a] -> SmallArray a+smallArrayFromListN n l =+ createSmallArray n+ (die "smallArrayFromListN" "uninitialized element") $ \sma ->+ let go !ix [] = if ix == n+ then return ()+ else die "smallArrayFromListN" "list length less than specified size"+ go !ix (x : xs) = if ix < n+ then do+ writeSmallArray sma ix x+ go (ix+1) xs+ else die "smallArrayFromListN" "list length greater than specified size"+ in go 0 l++-- | Create a 'SmallArray' from a list.+smallArrayFromList :: [a] -> SmallArray a+smallArrayFromList l = smallArrayFromListN (length l) l
+ src/Streamly/Internal/Data/Stream/Ahead.hs view
@@ -0,0 +1,733 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving#-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++-- |+-- Module : Streamly.Internal.Data.Stream.Ahead+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Ahead+ (+ AheadT+ , Ahead+ , aheadly+ , ahead+ )+where++import Control.Concurrent.MVar (putMVar, takeMVar)+import Control.Exception (assert)+import Control.Monad (void, when)+import Control.Monad.Base (MonadBase(..), liftBaseDefault)+import Control.Monad.Catch (MonadThrow, throwM)+-- import Control.Monad.Error.Class (MonadError(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader.Class (MonadReader(..))+import Control.Monad.State.Class (MonadState(..))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.Trans.Control (MonadBaseControl (..))+import Data.Heap (Heap, Entry(..))+import Data.IORef (IORef, readIORef, atomicModifyIORef, writeIORef)+import Data.Maybe (fromJust)+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif+import GHC.Exts (inline)++import qualified Data.Heap as H++import Streamly.Internal.Data.Stream.SVar (fromSVar)+import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.StreamK+ (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++import Prelude hiding (map)++#include "Instances.hs"++-------------------------------------------------------------------------------+-- Ahead+-------------------------------------------------------------------------------++-- Lookahead streams can execute multiple tasks concurrently, ahead of time,+-- but always serve them in the same order as they appear in the stream. To+-- implement lookahead streams efficiently we assign a sequence number to each+-- task when the task is picked up for execution. When the task finishes, the+-- output is tagged with the same sequence number and we rearrange the outputs+-- in sequence based on that number.+--+-- To explain the mechanism imagine that the current task at the head of the+-- stream has a "token" to yield to the outputQueue. The ownership of the token+-- is determined by the current sequence number is maintained in outputHeap.+-- Sequence number is assigned when a task is queued. When a thread dequeues a+-- task it picks up the sequence number as well and when the output is ready it+-- uses the sequence number to queue the output to the outputQueue.+--+-- The thread with current sequence number sends the output directly to the+-- outputQueue. Other threads push the output to the outputHeap. When the task+-- being queued on the heap is a stream of many elements we evaluate only the+-- first element and keep the rest of the unevaluated computation in the heap.+-- When such a task gets the "token" for outputQueue it evaluates and directly+-- yields all the elements to the outputQueue without checking for the+-- "token".+--+-- Note that no two outputs in the heap can have the same sequence numbers and+-- therefore we do not need a stable heap. We have also separated the buffer+-- for the current task (outputQueue) and the pending tasks (outputHeap) so+-- that the pending tasks cannot interfere with the current task. Note that for+-- a single task just the outputQueue is enough and for the case of many+-- threads just a heap is good enough. However we balance between these two+-- cases, so that both are efficient.+--+-- For bigger streams it may make sense to have separate buffers for each+-- stream. However, for singleton streams this may become inefficient. However,+-- if we do not have separate buffers, then the streams that come later in+-- sequence may hog the buffer, hindering the streams that are ahead. For this+-- reason we have a single element buffer limitation for the streams being+-- executed in advance.+--+-- This scheme works pretty efficiently with less than 40% extra overhead+-- compared to the Async streams where we do not have any kind of sequencing of+-- the outputs. It is especially devised so that we are most efficient when we+-- have short tasks and need just a single thread. Also when a thread yields+-- many items it can hold lockfree access to the outputQueue and do it+-- efficiently.+--+-- XXX Maybe we can start the ahead threads at a lower cpu and IO priority so+-- that they do not hog the resources and hinder the progress of the threads in+-- front of them.++-- Left associated ahead expressions are expensive. We start a new SVar for+-- each left associative expression. The queue is used only for right+-- associated expression, we queue the right expression and execute the left.+-- Thererefore the queue never has more than on item in it.+--+-- XXX Also note that limiting concurrency for cases like "take 10" would not+-- work well with left associative expressions, because we have no visibility+-- about how much the left side of the expression would yield.+--+-- XXX It may be a good idea to increment sequence numbers for each yield,+-- currently a stream on the left side of the expression may yield many+-- elements with the same sequene number. We can then use the seq number to+-- enforce yieldMax and yieldLImit as well.++-- Invariants:+--+-- * A worker should always ensure that it pushes all the consecutive items in+-- the heap to the outputQueue especially the items on behalf of the workers+-- that have already left when we were holding the token. This avoids deadlock+-- conditions when the later workers completion depends on the consumption of+-- earlier results. For more details see comments in the consumer pull side+-- code.++{-# INLINE underMaxHeap #-}+underMaxHeap ::+ SVar Stream m a+ -> Heap (Entry Int (AheadHeapEntry Stream m a))+ -> IO Bool+underMaxHeap sv hp = do+ (_, len) <- readIORef (outputQueue sv)++ -- XXX simplify this+ let maxHeap = case maxBufferLimit sv of+ Limited lim -> Limited $+ max 0 (lim - fromIntegral len)+ Unlimited -> Unlimited++ case maxHeap of+ Limited lim -> do+ active <- readIORef (workerCount sv)+ return $ H.size hp + active <= fromIntegral lim+ Unlimited -> return True++-- Return value:+-- True => stop+-- False => continue+preStopCheck ::+ SVar Stream m a+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Maybe Int)+ -> IO Bool+preStopCheck sv heap =+ -- check the stop condition under a lock before actually+ -- stopping so that the whole herd does not stop at once.+ withIORef heap $ \(hp, _) -> do+ heapOk <- underMaxHeap sv hp+ takeMVar (workerStopMVar sv)+ let stop = do+ putMVar (workerStopMVar sv) ()+ return True+ continue = do+ putMVar (workerStopMVar sv) ()+ return False+ if heapOk+ then+ case yieldRateInfo sv of+ Nothing -> continue+ Just yinfo -> do+ rateOk <- isBeyondMaxRate sv yinfo+ if rateOk then continue else stop+ else stop++abortExecution ::+ IORef ([Stream m a], Int)+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> Stream m a+ -> IO ()+abortExecution q sv winfo m = do+ reEnqueueAhead sv q m+ incrementYieldLimit sv+ sendStop sv winfo++-- XXX In absence of a "noyield" primitive (i.e. do not pre-empt inside a+-- critical section) from GHC RTS, we have a difficult problem. Assume we have+-- a 100,000 threads producing output and queuing it to the heap for+-- sequencing. The heap can be drained only by one thread at a time, any thread+-- that finds that heap can be drained now, takes a lock and starts draining+-- it, however the thread may get prempted in the middle of it holding the+-- lock. Since that thread is holding the lock, the other threads cannot pick+-- up the draining task, therefore they proceed to picking up the next task to+-- execute. If the draining thread could yield voluntarily at a point where it+-- has released the lock, then the next threads could pick up the draining+-- instead of executing more tasks. When there are 100,000 threads the drainer+-- gets a cpu share to run only 1:100000 of the time. This makes the heap+-- accumulate a lot of output when we the buffer size is large.+--+-- The solutions to this problem are:+-- 1) make the other threads wait in a queue until the draining finishes+-- 2) make the other threads queue and go away if draining is in progress+--+-- In both cases we give the drainer a chance to run more often.+--+processHeap+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef ([Stream m a], Int)+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> AheadHeapEntry Stream m a+ -> Int+ -> Bool -- we are draining the heap before we stop+ -> m ()+processHeap q heap st sv winfo entry sno stopping = loopHeap sno entry++ where++ stopIfNeeded ent seqNo r = do+ stopIt <- liftIO $ preStopCheck sv heap+ if stopIt+ then liftIO $ do+ -- put the entry back in the heap and stop+ requeueOnHeapTop heap (Entry seqNo ent) seqNo+ sendStop sv winfo+ else runStreamWithYieldLimit True seqNo r++ loopHeap seqNo ent =+ case ent of+ AheadEntryNull -> nextHeap seqNo+ AheadEntryPure a -> do+ -- Use 'send' directly so that we do not account this in worker+ -- latency as this will not be the real latency.+ -- Don't stop the worker in this case as we are just+ -- transferring available results from heap to outputQueue.+ void $ liftIO $ send sv (ChildYield a)+ nextHeap seqNo+ AheadEntryStream r ->+ if stopping+ then stopIfNeeded ent seqNo r+ else runStreamWithYieldLimit True seqNo r++ nextHeap prevSeqNo = do+ res <- liftIO $ dequeueFromHeapSeq heap (prevSeqNo + 1)+ case res of+ Ready (Entry seqNo hent) -> loopHeap seqNo hent+ Clearing -> liftIO $ sendStop sv winfo+ Waiting _ ->+ if stopping+ then do+ r <- liftIO $ preStopCheck sv heap+ if r+ then liftIO $ sendStop sv winfo+ else processWorkQueue prevSeqNo+ else inline processWorkQueue prevSeqNo++ processWorkQueue prevSeqNo = do+ work <- dequeueAhead q+ case work of+ Nothing -> liftIO $ sendStop sv winfo+ Just (m, seqNo) -> do+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if yieldLimitOk+ then+ if seqNo == prevSeqNo + 1+ then processWithToken q heap st sv winfo m seqNo+ else processWithoutToken q heap st sv winfo m seqNo+ else liftIO $ abortExecution q sv winfo m++ -- We do not stop the worker on buffer full here as we want to proceed to+ -- nextHeap anyway so that we can clear any subsequent entries. We stop+ -- only in yield continuation where we may have a remaining stream to be+ -- pushed on the heap.+ singleStreamFromHeap seqNo a = do+ void $ liftIO $ sendYield sv winfo (ChildYield a)+ nextHeap seqNo++ -- XXX when we have an unfinished stream on the heap we cannot account all+ -- the yields of that stream until it finishes, so if we have picked up+ -- and executed more actions beyond that in the parent stream and put them+ -- on the heap then they would eat up some yield limit which is not+ -- correct, we will think that our yield limit is over even though we have+ -- to yield items from unfinished stream before them. For this reason, if+ -- there are pending items in the heap we drain them unconditionally+ -- without considering the yield limit.+ runStreamWithYieldLimit continue seqNo r = do+ _ <- liftIO $ decrementYieldLimit sv+ if continue -- see comment above -- && yieldLimitOk+ then do+ let stop = do+ liftIO (incrementYieldLimit sv)+ nextHeap seqNo+ foldStreamShared st+ (yieldStreamFromHeap seqNo)+ (singleStreamFromHeap seqNo)+ stop+ r+ else liftIO $ do+ let ent = Entry seqNo (AheadEntryStream r)+ liftIO $ requeueOnHeapTop heap ent seqNo+ incrementYieldLimit sv+ sendStop sv winfo++ yieldStreamFromHeap seqNo a r = do+ continue <- liftIO $ sendYield sv winfo (ChildYield a)+ runStreamWithYieldLimit continue seqNo r++{-# NOINLINE drainHeap #-}+drainHeap+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef ([Stream m a], Int)+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+drainHeap q heap st sv winfo = do+ r <- liftIO $ dequeueFromHeap heap+ case r of+ Ready (Entry seqNo hent) ->+ processHeap q heap st sv winfo hent seqNo True+ _ -> liftIO $ sendStop sv winfo++data HeapStatus = HContinue | HStop+data WorkerStatus = Continue | Suspend++processWithoutToken+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef ([Stream m a], Int)+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> Stream m a+ -> Int+ -> m ()+processWithoutToken q heap st sv winfo m seqNo = do+ -- we have already decremented the yield limit for m+ let stop = do+ liftIO (incrementYieldLimit sv)+ -- If the stream stops without yielding anything, and we do not put+ -- anything on heap, but if heap was waiting for this seq number+ -- then it will keep waiting forever, because we are never going to+ -- put it on heap. So we have to put a null entry on heap even when+ -- we stop.+ toHeap AheadEntryNull+ mrun = runInIO $ svarMrun sv++ r <- liftIO $ mrun $+ foldStreamShared st+ (\a r -> toHeap $ AheadEntryStream $ K.cons a r)+ (toHeap . AheadEntryPure)+ stop+ m+ res <- restoreM r+ case res of+ Continue -> workLoopAhead q heap st sv winfo+ Suspend -> drainHeap q heap st sv winfo++ where++ -- XXX to reduce contention each CPU can have its own heap+ toHeap ent = do+ -- Heap insertion is an expensive affair so we use a non CAS based+ -- modification, otherwise contention and retries can make a thread+ -- context switch and throw it behind other threads which come later in+ -- sequence.+ newHp <- liftIO $ atomicModifyIORef heap $ \(hp, snum) ->+ let hp' = H.insert (Entry seqNo ent) hp+ in assert (heapIsSane snum seqNo) ((hp', snum), hp')++ when (svarInspectMode sv) $+ liftIO $ do+ maxHp <- readIORef (maxHeapSize $ svarStats sv)+ when (H.size newHp > maxHp) $+ writeIORef (maxHeapSize $ svarStats sv) (H.size newHp)++ heapOk <- liftIO $ underMaxHeap sv newHp+ status <-+ case yieldRateInfo sv of+ Nothing -> return HContinue+ Just yinfo ->+ case winfo of+ Just info -> do+ rateOk <- liftIO $ workerRateControl sv yinfo info+ if rateOk+ then return HContinue+ else return HStop+ Nothing -> return HContinue++ if heapOk+ then+ case status of+ HContinue -> return Continue+ HStop -> return Suspend+ else return Suspend++data TokenWorkerStatus = TokenContinue Int | TokenSuspend++processWithToken+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef ([Stream m a], Int)+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> Stream m a+ -> Int+ -> m ()+processWithToken q heap st sv winfo action sno = do+ -- Note, we enter this function with yield limit already decremented+ -- XXX deduplicate stop in all invocations+ let stop = do+ liftIO (incrementYieldLimit sv)+ return $ TokenContinue (sno + 1)+ mrun = runInIO $ svarMrun sv++ r <- liftIO $ mrun $+ foldStreamShared st (yieldOutput sno) (singleOutput sno) stop action++ res <- restoreM r+ case res of+ TokenContinue seqNo -> loopWithToken seqNo+ TokenSuspend -> drainHeap q heap st sv winfo++ where++ singleOutput seqNo a = do+ continue <- liftIO $ sendYield sv winfo (ChildYield a)+ if continue+ then return $ TokenContinue (seqNo + 1)+ else do+ liftIO $ updateHeapSeq heap (seqNo + 1)+ return TokenSuspend++ -- XXX use a wrapper function around stop so that we never miss+ -- incrementing the yield in a stop continuation. Essentiatlly all+ -- "unstream" calls in this function must increment yield limit on stop.+ yieldOutput seqNo a r = do+ continue <- liftIO $ sendYield sv winfo (ChildYield a)+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if continue && yieldLimitOk+ then do+ let stop = do+ liftIO (incrementYieldLimit sv)+ return $ TokenContinue (seqNo + 1)+ foldStreamShared st+ (yieldOutput seqNo)+ (singleOutput seqNo)+ stop+ r+ else do+ let ent = Entry seqNo (AheadEntryStream r)+ liftIO $ requeueOnHeapTop heap ent seqNo+ liftIO $ incrementYieldLimit sv+ return TokenSuspend++ loopWithToken nextSeqNo = do+ work <- dequeueAhead q+ case work of+ Nothing -> do+ liftIO $ updateHeapSeq heap nextSeqNo+ workLoopAhead q heap st sv winfo++ Just (m, seqNo) -> do+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ let undo = liftIO $ do+ updateHeapSeq heap nextSeqNo+ reEnqueueAhead sv q m+ incrementYieldLimit sv+ if yieldLimitOk+ then+ if seqNo == nextSeqNo+ then do+ let stop = do+ liftIO (incrementYieldLimit sv)+ return $ TokenContinue (seqNo + 1)+ mrun = runInIO $ svarMrun sv+ r <- liftIO $ mrun $+ foldStreamShared st+ (yieldOutput seqNo)+ (singleOutput seqNo)+ stop+ m+ res <- restoreM r+ case res of+ TokenContinue seqNo1 -> loopWithToken seqNo1+ TokenSuspend -> drainHeap q heap st sv winfo++ else+ -- To avoid a race when another thread puts something+ -- on the heap and goes away, the consumer will not get+ -- a doorBell and we will not clear the heap before+ -- executing the next action. If the consumer depends+ -- on the output that is stuck in the heap then this+ -- will result in a deadlock. So we always clear the+ -- heap before executing the next action.+ undo >> workLoopAhead q heap st sv winfo+ else undo >> drainHeap q heap st sv winfo++-- XXX the yield limit changes increased the performance overhead by 30-40%.+-- Just like AsyncT we can use an implementation without yeidlimit and even+-- without pacing code to keep the performance higher in the unlimited and+-- unpaced case.+--+-- XXX The yieldLimit stuff is pretty invasive. We can instead do it by using+-- three hooks, a pre-execute hook, a yield hook and a stop hook. In fact these+-- hooks can be used for a more general implementation to even check predicates+-- and not just yield limit.++-- XXX we can remove the sv parameter as it can be derived from st++workLoopAhead+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef ([Stream m a], Int)+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+workLoopAhead q heap st sv winfo = do+ r <- liftIO $ dequeueFromHeap heap+ case r of+ Ready (Entry seqNo hent) ->+ processHeap q heap st sv winfo hent seqNo False+ Clearing -> liftIO $ sendStop sv winfo+ Waiting _ -> do+ -- Before we execute the next item from the work queue we check+ -- if we are beyond the yield limit. It is better to check the+ -- yield limit before we pick up the next item. Otherwise we+ -- may have already started more tasks even though we may have+ -- reached the yield limit. We can avoid this by taking active+ -- workers into account, but that is not as reliable, because+ -- workers may go away without picking up work and yielding a+ -- value.+ --+ -- Rate control can be done either based on actual yields in+ -- the output queue or based on any yield either to the heap or+ -- to the output queue. In both cases we may have one issue or+ -- the other. We chose to do this based on actual yields to the+ -- output queue because it makes the code common to both async+ -- and ahead streams.+ --+ work <- dequeueAhead q+ case work of+ Nothing -> liftIO $ sendStop sv winfo+ Just (m, seqNo) -> do+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if yieldLimitOk+ then+ if seqNo == 0+ then processWithToken q heap st sv winfo m seqNo+ else processWithoutToken q heap st sv winfo m seqNo+ -- If some worker decremented the yield limit but then+ -- did not yield anything and therefore incremented it+ -- later, then if we did not requeue m here we may find+ -- the work queue empty and therefore miss executing+ -- the remaining action.+ else liftIO $ abortExecution q sv winfo m++-------------------------------------------------------------------------------+-- WAhead+-------------------------------------------------------------------------------++-- XXX To be implemented. Use a linked queue like WAsync and put back the+-- remaining computation at the back of the queue instead of the heap, and+-- increment the sequence number.++-- The only difference between forkSVarAsync and this is that we run the left+-- computation without a shared SVar.+forkSVarAhead :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+forkSVarAhead m1 m2 = mkStream $ \st yld sng stp -> do+ sv <- newAheadVar st (concurrently (toStream m1) (toStream m2))+ workLoopAhead+ foldStream st yld sng stp (fromSVar sv)+ where+ concurrently ma mb = mkStream $ \st yld sng stp -> do+ liftIO $ enqueue (fromJust $ streamVar st) mb+ foldStream st yld sng stp ma++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AheadT'.+-- Merges two streams sequentially but with concurrent lookahead.+--+-- @since 0.3.0+{-# INLINE ahead #-}+ahead :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+ahead m1 m2 = mkStream $ \st yld sng stp ->+ case streamVar st of+ Just sv | svarStyle sv == AheadVar -> do+ liftIO $ enqueue sv (toStream m2)+ -- Always run the left side on a new SVar to avoid complexity in+ -- sequencing results. This means the left side cannot further+ -- split into more ahead computations on the same SVar.+ foldStream st yld sng stp m1+ _ -> foldStreamShared st yld sng stp (forkSVarAhead m1 m2)++-- | XXX we can implement it more efficienty by directly implementing instead+-- of combining streams using ahead.+{-# INLINE consMAhead #-}+{-# SPECIALIZE consMAhead :: IO a -> AheadT IO a -> AheadT IO a #-}+consMAhead :: MonadAsync m => m a -> AheadT m a -> AheadT m a+consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)++------------------------------------------------------------------------------+-- AheadT+------------------------------------------------------------------------------++-- | The 'Semigroup' operation for 'AheadT' appends two streams. The combined+-- stream behaves like a single stream with the actions from the second stream+-- appended to the first stream. The combined stream is evaluated in the+-- speculative style. This operation can be used to fold an infinite lazy+-- container of streams.+--+-- @+-- import "Streamly"+-- import qualified "Streamly.Prelude" as S+-- import Control.Concurrent+--+-- main = do+-- xs \<- S.'toList' . 'aheadly' $ (p 1 |: p 2 |: nil) <> (p 3 |: p 4 |: nil)+-- print xs+-- where p n = threadDelay 1000000 >> return n+-- @+-- @+-- [1,2,3,4]+-- @+--+-- Any exceptions generated by a constituent stream are propagated to the+-- output stream.+--+-- The monad instance of 'AheadT' may run each monadic continuation (bind)+-- concurrently in a speculative manner, performing side effects in a partially+-- ordered manner but producing the outputs in an ordered manner like+-- 'SerialT'.+--+-- @+-- main = S.drain . 'aheadly' $ do+-- n <- return 3 \<\> return 2 \<\> return 1+-- S.yieldM $ do+-- threadDelay (n * 1000000)+-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)+-- @+-- @+-- ThreadId 40: Delay 1+-- ThreadId 39: Delay 2+-- ThreadId 38: Delay 3+-- @+--+-- @since 0.3.0+newtype AheadT m a = AheadT {getAheadT :: Stream m a}+ deriving (MonadTrans)++-- | A serial IO stream of elements of type @a@ with concurrent lookahead. See+-- 'AheadT' documentation for more details.+--+-- @since 0.3.0+type Ahead = AheadT IO++-- | Fix the type of a polymorphic stream as 'AheadT'.+--+-- @since 0.3.0+aheadly :: IsStream t => AheadT m a -> t m a+aheadly = K.adapt++instance IsStream AheadT where+ toStream = getAheadT+ fromStream = AheadT+ consM = consMAhead+ (|:) = consMAhead++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++{-# INLINE mappendAhead #-}+{-# SPECIALIZE mappendAhead :: AheadT IO a -> AheadT IO a -> AheadT IO a #-}+mappendAhead :: MonadAsync m => AheadT m a -> AheadT m a -> AheadT m a+mappendAhead m1 m2 = fromStream $ ahead (toStream m1) (toStream m2)++instance MonadAsync m => Semigroup (AheadT m a) where+ (<>) = mappendAhead++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance MonadAsync m => Monoid (AheadT m a) where+ mempty = K.nil+ mappend = (<>)++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++{-# INLINE concatMapAhead #-}+{-# SPECIALIZE concatMapAhead :: (a -> AheadT IO b) -> AheadT IO a -> AheadT IO b #-}+concatMapAhead :: MonadAsync m => (a -> AheadT m b) -> AheadT m a -> AheadT m b+concatMapAhead f m = fromStream $+ K.concatMapBy ahead (\a -> K.adapt $ f a) (K.adapt m)++{-# INLINE apAhead #-}+apAhead :: MonadAsync m => AheadT m (a -> b) -> AheadT m a -> AheadT m b+apAhead (AheadT m1) (AheadT m2) =+ let f x1 = K.concatMapBy ahead (pure . x1) m2+ in AheadT $ K.concatMapBy ahead f m1++instance (Monad m, MonadAsync m) => Applicative (AheadT m) where+ {-# INLINE pure #-}+ pure = AheadT . K.yield+ {-# INLINE (<*>) #-}+ (<*>) = apAhead++instance MonadAsync m => Monad (AheadT m) where+ return = pure+ {-# INLINE (>>=) #-}+ (>>=) = flip concatMapAhead++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++MONAD_COMMON_INSTANCES(AheadT, MONADPARALLEL)
+ src/Streamly/Internal/Data/Stream/Async.hs view
@@ -0,0 +1,998 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving#-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.Async+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Async+ (+ AsyncT+ , Async+ , asyncly+ , async+ , (<|) --deprecated+ , mkAsync+ , mkAsyncK++ , WAsyncT+ , WAsync+ , wAsyncly+ , wAsync+ )+where++import Control.Concurrent (myThreadId)+import Control.Monad.Base (MonadBase(..), liftBaseDefault)+import Control.Monad.Catch (MonadThrow, throwM)+import Control.Monad.Trans.Control (MonadBaseControl (..))+import Control.Concurrent.MVar (newEmptyMVar)+-- import Control.Monad.Error.Class (MonadError(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader.Class (MonadReader(..))+import Control.Monad.State.Class (MonadState(..))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)+import Data.IORef (IORef, newIORef, readIORef)+import Data.Maybe (fromJust)+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif++import Prelude hiding (map)+import qualified Data.Set as S++import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)+import Streamly.Internal.Data.Stream.SVar (fromSVar)+import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.StreamK+ (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++#include "Instances.hs"++-------------------------------------------------------------------------------+-- Async+-------------------------------------------------------------------------------++data WorkerStatus = Continue | Suspend++{-# INLINE workLoopLIFO #-}+workLoopLIFO+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef [Stream m a]+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+workLoopLIFO q st sv winfo = run++ where++ mrun = runInIO $ svarMrun sv+ run = do+ work <- dequeue+ let stop = liftIO $ sendStop sv winfo+ case work of+ Nothing -> stop+ Just m -> do+ -- XXX when we finish we need to send the monadic state back to+ -- the parent so that the state can be merged back. We capture+ -- and return the state in the stop continuation.+ --+ -- Instead of using the run function we can just restore the+ -- monad state here. That way it can work easily for+ -- distributed case as well.+ r <- liftIO $ mrun $+ foldStreamShared st yieldk single (return Continue) m+ res <- restoreM r+ case res of+ Continue -> run+ Suspend -> stop++ single a = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ return $ if res then Continue else Suspend++ yieldk a r = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ if res+ then foldStreamShared st yieldk single (return Continue) r+ else liftIO $ do+ -- XXX we also need to save the monadic state here+ enqueueLIFO sv q r+ return Suspend++ dequeue = liftIO $ atomicModifyIORefCAS q $ \case+ [] -> ([], Nothing)+ x : xs -> (xs, Just x)++-- We duplicate workLoop for yield limit and no limit cases because it has+-- around 40% performance overhead in the worst case.+--+-- XXX we can pass yinfo directly as an argument here so that we do not have to+-- make a check every time.+{-# INLINE workLoopLIFOLimited #-}+workLoopLIFOLimited+ :: (MonadIO m, MonadBaseControl IO m)+ => IORef [Stream m a]+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+workLoopLIFOLimited q st sv winfo = run++ where++ mrun = runInIO $ svarMrun sv+ incrContinue = liftIO (incrementYieldLimit sv) >> return Continue+ run = do+ work <- dequeue+ let stop = liftIO $ sendStop sv winfo+ case work of+ Nothing -> stop+ Just m -> do+ -- XXX This is just a best effort minimization of concurrency+ -- to the yield limit. If the stream is made of concurrent+ -- streams we do not reserve the yield limit in the constituent+ -- streams before executing the action. This can be done+ -- though, by sharing the yield limit ref with downstream+ -- actions via state passing. Just a todo.+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if yieldLimitOk+ then do+ r <- liftIO $ mrun $+ foldStreamShared st yieldk single incrContinue m+ res <- restoreM r+ case res of+ Continue -> run+ Suspend -> stop+ -- Avoid any side effects, undo the yield limit decrement if we+ -- never yielded anything.+ else liftIO $ do+ enqueueLIFO sv q m+ incrementYieldLimit sv+ sendStop sv winfo++ single a = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ return $ if res then Continue else Suspend++ -- XXX can we pass on the yield limit downstream to limit the concurrency+ -- of constituent streams.+ yieldk a r = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if res && yieldLimitOk+ then foldStreamShared st yieldk single incrContinue r+ else liftIO $ do+ incrementYieldLimit sv+ enqueueLIFO sv q r+ return Suspend++ dequeue = liftIO $ atomicModifyIORefCAS q $ \case+ [] -> ([], Nothing)+ x : xs -> (xs, Just x)++-------------------------------------------------------------------------------+-- WAsync+-------------------------------------------------------------------------------++-- XXX we can remove sv as it is derivable from st++{-# INLINE workLoopFIFO #-}+workLoopFIFO+ :: (MonadIO m, MonadBaseControl IO m)+ => LinkedQueue (Stream m a)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+workLoopFIFO q st sv winfo = run++ where++ mrun = runInIO $ svarMrun sv+ run = do+ work <- liftIO $ tryPopR q+ let stop = liftIO $ sendStop sv winfo+ case work of+ Nothing -> stop+ Just m -> do+ r <- liftIO $ mrun $+ foldStreamShared st yieldk single (return Continue) m+ res <- restoreM r+ case res of+ Continue -> run+ Suspend -> stop++ single a = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ return $ if res then Continue else Suspend++ -- XXX in general we would like to yield "n" elements from a single stream+ -- before moving on to the next. Single element granularity could be too+ -- expensive in certain cases. Similarly, we can use time limit for+ -- yielding.+ yieldk a r = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ liftIO $ enqueueFIFO sv q r+ return $ if res then Continue else Suspend++{-# INLINE workLoopFIFOLimited #-}+workLoopFIFOLimited+ :: (MonadIO m, MonadBaseControl IO m)+ => LinkedQueue (Stream m a)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m ()+workLoopFIFOLimited q st sv winfo = run++ where++ mrun = runInIO $ svarMrun sv+ incrContinue = liftIO (incrementYieldLimit sv) >> return Continue+ run = do+ work <- liftIO $ tryPopR q+ let stop = liftIO $ sendStop sv winfo+ case work of+ Nothing -> stop+ Just m -> do+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if yieldLimitOk+ then do+ r <- liftIO $ mrun $+ foldStreamShared st yieldk single incrContinue m+ res <- restoreM r+ case res of+ Continue -> run+ Suspend -> stop+ else liftIO $ do+ enqueueFIFO sv q m+ incrementYieldLimit sv+ sendStop sv winfo++ single a = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ return $ if res then Continue else Suspend++ yieldk a r = do+ res <- liftIO $ sendYield sv winfo (ChildYield a)+ liftIO $ enqueueFIFO sv q r+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if res && yieldLimitOk+ then return Continue+ else liftIO $ do+ incrementYieldLimit sv+ return Suspend++-------------------------------------------------------------------------------+-- SVar creation+-- This code belongs in SVar.hs but is kept here for perf reasons+-------------------------------------------------------------------------------++-- XXX we have this function in this file because passing runStreamLIFO as a+-- function argument to this function results in a perf degradation of more+-- than 10%. Need to investigate what the root cause is.+-- Interestingly, the same thing does not make any difference for Ahead.+getLifoSVar :: forall m a. MonadAsync m+ => State Stream m a -> RunInIO m -> IO (SVar Stream m a)+getLifoSVar st mrun = do+ outQ <- newIORef ([], 0)+ outQMv <- newEmptyMVar+ active <- newIORef 0+ wfw <- newIORef False+ running <- newIORef S.empty+ q <- newIORef []+ yl <- case getYieldLimit st of+ Nothing -> return Nothing+ Just x -> Just <$> newIORef x+ rateInfo <- getYieldRateInfo st++ stats <- newSVarStats+ tid <- myThreadId++ let isWorkFinished _ = null <$> readIORef q++ let isWorkFinishedLimited sv = do+ yieldsDone <-+ case remainingWork sv of+ Just ref -> do+ n <- readIORef ref+ return (n <= 0)+ Nothing -> return False+ qEmpty <- null <$> readIORef q+ return $ qEmpty || yieldsDone++ let getSVar :: SVar Stream m a+ -> (SVar Stream m a -> m [ChildEvent a])+ -> (SVar Stream m a -> m Bool)+ -> (SVar Stream m a -> IO Bool)+ -> (IORef [Stream m a]+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m())+ -> SVar Stream m a+ getSVar sv readOutput postProc workDone wloop = SVar+ { outputQueue = outQ+ , outputQueueFromConsumer = undefined+ , remainingWork = yl+ , maxBufferLimit = getMaxBuffer st+ , pushBufferSpace = undefined+ , pushBufferPolicy = undefined+ , pushBufferMVar = undefined+ , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)+ , yieldRateInfo = rateInfo+ , outputDoorBell = outQMv+ , outputDoorBellFromConsumer = undefined+ , readOutputQ = readOutput sv+ , postProcess = postProc sv+ , workerThreads = running+ , workLoop = wloop q st{streamVar = Just sv} sv+ , enqueue = enqueueLIFO sv q+ , isWorkDone = workDone sv+ , isQueueDone = workDone sv+ , needDoorBell = wfw+ , svarStyle = AsyncVar+ , svarStopStyle = StopNone+ , svarStopBy = undefined+ , svarMrun = mrun+ , workerCount = active+ , accountThread = delThread sv+ , workerStopMVar = undefined+ , svarRef = Nothing+ , svarInspectMode = getInspectMode st+ , svarCreator = tid+ , aheadWorkQueue = undefined+ , outputHeap = undefined+ , svarStats = stats+ }++ let sv =+ case getStreamRate st of+ Nothing ->+ case getYieldLimit st of+ Nothing -> getSVar sv readOutputQBounded+ postProcessBounded+ isWorkFinished+ workLoopLIFO+ Just _ -> getSVar sv readOutputQBounded+ postProcessBounded+ isWorkFinishedLimited+ workLoopLIFOLimited+ Just _ ->+ case getYieldLimit st of+ Nothing -> getSVar sv readOutputQPaced+ postProcessPaced+ isWorkFinished+ workLoopLIFO+ Just _ -> getSVar sv readOutputQPaced+ postProcessPaced+ isWorkFinishedLimited+ workLoopLIFOLimited+ in return sv++getFifoSVar :: forall m a. MonadAsync m+ => State Stream m a -> RunInIO m -> IO (SVar Stream m a)+getFifoSVar st mrun = do+ outQ <- newIORef ([], 0)+ outQMv <- newEmptyMVar+ active <- newIORef 0+ wfw <- newIORef False+ running <- newIORef S.empty+ q <- newQ+ yl <- case getYieldLimit st of+ Nothing -> return Nothing+ Just x -> Just <$> newIORef x+ rateInfo <- getYieldRateInfo st++ stats <- newSVarStats+ tid <- myThreadId++ let isWorkFinished _ = nullQ q+ let isWorkFinishedLimited sv = do+ yieldsDone <-+ case remainingWork sv of+ Just ref -> do+ n <- readIORef ref+ return (n <= 0)+ Nothing -> return False+ qEmpty <- nullQ q+ return $ qEmpty || yieldsDone++ let getSVar :: SVar Stream m a+ -> (SVar Stream m a -> m [ChildEvent a])+ -> (SVar Stream m a -> m Bool)+ -> (SVar Stream m a -> IO Bool)+ -> (LinkedQueue (Stream m a)+ -> State Stream m a+ -> SVar Stream m a+ -> Maybe WorkerInfo+ -> m())+ -> SVar Stream m a+ getSVar sv readOutput postProc workDone wloop = SVar+ { outputQueue = outQ+ , outputQueueFromConsumer = undefined+ , remainingWork = yl+ , maxBufferLimit = getMaxBuffer st+ , pushBufferSpace = undefined+ , pushBufferPolicy = undefined+ , pushBufferMVar = undefined+ , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)+ , yieldRateInfo = rateInfo+ , outputDoorBell = outQMv+ , outputDoorBellFromConsumer = undefined+ , readOutputQ = readOutput sv+ , postProcess = postProc sv+ , workerThreads = running+ , workLoop = wloop q st{streamVar = Just sv} sv+ , enqueue = enqueueFIFO sv q+ , isWorkDone = workDone sv+ , isQueueDone = workDone sv+ , needDoorBell = wfw+ , svarStyle = WAsyncVar+ , svarStopStyle = StopNone+ , svarStopBy = undefined+ , svarMrun = mrun+ , workerCount = active+ , accountThread = delThread sv+ , workerStopMVar = undefined+ , svarRef = Nothing+ , svarInspectMode = getInspectMode st+ , svarCreator = tid+ , aheadWorkQueue = undefined+ , outputHeap = undefined+ , svarStats = stats+ }++ let sv =+ case getStreamRate st of+ Nothing ->+ case getYieldLimit st of+ Nothing -> getSVar sv readOutputQBounded+ postProcessBounded+ isWorkFinished+ workLoopFIFO+ Just _ -> getSVar sv readOutputQBounded+ postProcessBounded+ isWorkFinishedLimited+ workLoopFIFOLimited+ Just _ ->+ case getYieldLimit st of+ Nothing -> getSVar sv readOutputQPaced+ postProcessPaced+ isWorkFinished+ workLoopFIFO+ Just _ -> getSVar sv readOutputQPaced+ postProcessPaced+ isWorkFinishedLimited+ workLoopFIFOLimited+ in return sv++{-# INLINABLE newAsyncVar #-}+newAsyncVar :: MonadAsync m+ => State Stream m a -> Stream m a -> m (SVar Stream m a)+newAsyncVar st m = do+ mrun <- captureMonadState+ sv <- liftIO $ getLifoSVar st mrun+ sendFirstWorker sv m++-- | Generate a stream asynchronously to keep it buffered, lazily consume+-- from the buffer.+--+-- /Internal/+--+{-# INLINABLE mkAsyncK #-}+mkAsyncK :: (IsStream t, MonadAsync m) => t m a -> t m a+mkAsyncK m = mkStream $ \st yld sng stp -> do+ sv <- newAsyncVar (adaptState st) (toStream m)+ foldStream st yld sng stp $ fromSVar sv++{-# INLINE_NORMAL mkAsyncD #-}+mkAsyncD :: MonadAsync m => D.Stream m a -> D.Stream m a+mkAsyncD m = D.Stream step Nothing+ where++ step gst Nothing = do+ sv <- newAsyncVar gst (D.fromStreamD m)+ return $ D.Skip $ Just $ D.fromSVar sv++ step gst (Just (D.UnStream step1 st)) = do+ r <- step1 gst st+ return $ case r of+ D.Yield a s -> D.Yield a (Just $ D.Stream step1 s)+ D.Skip s -> D.Skip (Just $ D.Stream step1 s)+ D.Stop -> D.Stop++-- This is slightly faster than the CPS version above+--+-- | Make the stream producer and consumer run concurrently by introducing a+-- buffer between them. The producer thread evaluates the input stream until+-- the buffer fills, it terminates if the buffer is full and a worker thread is+-- kicked off again to evaluate the remaining stream when there is space in the+-- buffer. The consumer consumes the stream lazily from the buffer.+--+-- /Internal/+--+{-# INLINE_NORMAL mkAsync #-}+mkAsync :: (K.IsStream t, MonadAsync m) => t m a -> t m a+mkAsync = D.fromStreamD . mkAsyncD . D.toStreamD++-- | Create a new SVar and enqueue one stream computation on it.+{-# INLINABLE newWAsyncVar #-}+newWAsyncVar :: MonadAsync m+ => State Stream m a -> Stream m a -> m (SVar Stream m a)+newWAsyncVar st m = do+ mrun <- captureMonadState+ sv <- liftIO $ getFifoSVar st mrun+ sendFirstWorker sv m++------------------------------------------------------------------------------+-- Running streams concurrently+------------------------------------------------------------------------------++-- Concurrency rate control.+--+-- Our objective is to create more threads on demand if the consumer is running+-- faster than us. As soon as we encounter a concurrent composition we create a+-- push pull pair of threads. We use an SVar for communication between the+-- consumer, pulling from the SVar and the producer who is pushing to the SVar.+-- The producer creates more threads if the SVar drains and becomes empty, that+-- is the consumer is running faster.+--+-- XXX Note 1: This mechanism can be problematic if the initial production+-- latency is high, we may end up creating too many threads. So we need some+-- way to monitor and use the latency as well. Having a limit on the dispatches+-- (programmer controlled) may also help.+--+-- TBD Note 2: We may want to run computations at the lower level of the+-- composition tree serially even when they are composed using a parallel+-- combinator. We can use 'serial' in place of 'async' and 'wSerial' in+-- place of 'wAsync'. If we find that an SVar immediately above a computation+-- gets drained empty we can switch to parallelizing the computation. For that+-- we can use a state flag to fork the rest of the computation at any point of+-- time inside the Monad bind operation if the consumer is running at a faster+-- speed.+--+-- TBD Note 3: the binary operation ('parallel') composition allows us to+-- dispatch a chunkSize of only 1. If we have to dispatch in arbitrary+-- chunksizes we will need to compose the parallel actions using a data+-- constructor (A Free container) instead so that we can divide it in chunks of+-- arbitrary size before dispatching. If the stream is composed of+-- hierarchically composed grains of different sizes then we can always switch+-- to a desired granularity depending on the consumer speed.+--+-- TBD Note 4: for pure work (when we are not in the IO monad) we can divide it+-- into just the number of CPUs.++-- | Join two computations on the currently running 'SVar' queue for concurrent+-- execution. When we are using parallel composition, an SVar is passed around+-- as a state variable. We try to schedule a new parallel computation on the+-- SVar passed to us. The first time, when no SVar exists, a new SVar is+-- created. Subsequently, 'joinStreamVarAsync' may get called when a computation+-- already scheduled on the SVar is further evaluated. For example, when (a+-- `parallel` b) is evaluated it calls a 'joinStreamVarAsync' to put 'a' and 'b' on+-- the current scheduler queue.+--+-- The 'SVarStyle' required by the current composition context is passed as one+-- of the parameters. If the scheduling and composition style of the new+-- computation being scheduled is different than the style of the current SVar,+-- then we create a new SVar and schedule it on that. The newly created SVar+-- joins as one of the computations on the current SVar queue.+--+-- Cases when we need to switch to a new SVar:+--+-- * (x `parallel` y) `parallel` (t `parallel` u) -- all of them get scheduled on the same SVar+-- * (x `parallel` y) `parallel` (t `async` u) -- @t@ and @u@ get scheduled on a new child SVar+-- because of the scheduling policy change.+-- * if we 'adapt' a stream of type 'async' to a stream of type+-- 'Parallel', we create a new SVar at the transitioning bind.+-- * When the stream is switching from disjunctive composition to conjunctive+-- composition and vice-versa we create a new SVar to isolate the scheduling+-- of the two.++forkSVarAsync :: (IsStream t, MonadAsync m)+ => SVarStyle -> t m a -> t m a -> t m a+forkSVarAsync style m1 m2 = mkStream $ \st yld sng stp -> do+ sv <- case style of+ AsyncVar -> newAsyncVar st (concurrently (toStream m1) (toStream m2))+ WAsyncVar -> newWAsyncVar st (concurrently (toStream m1) (toStream m2))+ _ -> error "illegal svar type"+ foldStream st yld sng stp $ fromSVar sv+ where+ concurrently ma mb = mkStream $ \st yld sng stp -> do+ liftIO $ enqueue (fromJust $ streamVar st) mb+ foldStreamShared st yld sng stp ma++{-# INLINE joinStreamVarAsync #-}+joinStreamVarAsync :: (IsStream t, MonadAsync m)+ => SVarStyle -> t m a -> t m a -> t m a+joinStreamVarAsync style m1 m2 = mkStream $ \st yld sng stp ->+ case streamVar st of+ Just sv | svarStyle sv == style -> do+ liftIO $ enqueue sv (toStream m2)+ foldStreamShared st yld sng stp m1+ _ -> foldStreamShared st yld sng stp (forkSVarAsync style m1 m2)++------------------------------------------------------------------------------+-- Semigroup and Monoid style compositions for parallel actions+------------------------------------------------------------------------------++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.+-- Merges two streams possibly concurrently, preferring the+-- elements from the left one when available.+--+-- @since 0.2.0+{-# INLINE async #-}+async :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+async = joinStreamVarAsync AsyncVar++-- | Same as 'async'.+--+-- @since 0.1.0+{-# DEPRECATED (<|) "Please use 'async' instead." #-}+{-# INLINE (<|) #-}+(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+(<|) = async++-- IMPORTANT: using a monomorphically typed and SPECIALIZED consMAsync makes a+-- huge difference in the performance of consM in IsStream instance even we+-- have a SPECIALIZE in the instance.+--+-- | XXX we can implement it more efficienty by directly implementing instead+-- of combining streams using async.+{-# INLINE consMAsync #-}+{-# SPECIALIZE consMAsync :: IO a -> AsyncT IO a -> AsyncT IO a #-}+consMAsync :: MonadAsync m => m a -> AsyncT m a -> AsyncT m a+consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)++------------------------------------------------------------------------------+-- AsyncT+------------------------------------------------------------------------------++-- | The 'Semigroup' operation (@<>@) for 'AsyncT' merges two streams+-- concurrently with priority given to the first stream. In @s1 <> s2 <> s3+-- ...@ the streams s1, s2 and s3 are scheduled for execution in that order.+-- Multiple scheduled streams may be executed concurrently and the elements+-- generated by them are served to the consumer as and when they become+-- available. This behavior is similar to the scheduling and execution behavior+-- of actions in a single async stream.+--+-- Since only a finite number of streams are executed concurrently, this+-- operation can be used to fold an infinite lazy container of streams.+--+-- @+-- import "Streamly"+-- import qualified "Streamly.Prelude" as S+-- import Control.Concurrent+--+-- main = (S.toList . 'asyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print+-- @+-- @+-- [1,2,3,4]+-- @+--+-- Any exceptions generated by a constituent stream are propagated to the+-- output stream. The output and exceptions from a single stream are guaranteed+-- to arrive in the same order in the resulting stream as they were generated+-- in the input stream. However, the relative ordering of elements from+-- different streams in the resulting stream can vary depending on scheduling+-- and generation delays.+--+-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration+-- concurrently based on demand. More concurrent iterations are started only+-- if the previous iterations are not able to produce enough output for the+-- consumer.+--+-- @+-- main = 'drain' . 'asyncly' $ do+-- n <- return 3 \<\> return 2 \<\> return 1+-- S.yieldM $ do+-- threadDelay (n * 1000000)+-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)+-- @+-- @+-- ThreadId 40: Delay 1+-- ThreadId 39: Delay 2+-- ThreadId 38: Delay 3+-- @+--+-- @since 0.1.0+newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}+ deriving (MonadTrans)++-- | A demand driven left biased parallely composing IO stream of elements of+-- type @a@. See 'AsyncT' documentation for more details.+--+-- @since 0.2.0+type Async = AsyncT IO++-- | Fix the type of a polymorphic stream as 'AsyncT'.+--+-- @since 0.1.0+asyncly :: IsStream t => AsyncT m a -> t m a+asyncly = adapt++instance IsStream AsyncT where+ toStream = getAsyncT+ fromStream = AsyncT+ consM = consMAsync+ (|:) = consMAsync++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++-- Monomorphically typed version of "async" for better performance of Semigroup+-- instance.+{-# INLINE mappendAsync #-}+{-# SPECIALIZE mappendAsync :: AsyncT IO a -> AsyncT IO a -> AsyncT IO a #-}+mappendAsync :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a+mappendAsync m1 m2 = fromStream $ async (toStream m1) (toStream m2)++instance MonadAsync m => Semigroup (AsyncT m a) where+ (<>) = mappendAsync++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance MonadAsync m => Monoid (AsyncT m a) where+ mempty = K.nil+ mappend = (<>)++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++{-# INLINE apAsync #-}+{-# SPECIALIZE apAsync :: AsyncT IO (a -> b) -> AsyncT IO a -> AsyncT IO b #-}+apAsync :: MonadAsync m => AsyncT m (a -> b) -> AsyncT m a -> AsyncT m b+apAsync (AsyncT m1) (AsyncT m2) =+ let f x1 = K.concatMapBy async (pure . x1) m2+ in AsyncT $ K.concatMapBy async f m1++instance (Monad m, MonadAsync m) => Applicative (AsyncT m) where+ {-# INLINE pure #-}+ pure = AsyncT . K.yield+ {-# INLINE (<*>) #-}+ (<*>) = apAsync++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++-- GHC: if we change the implementation of bindWith with arguments in a+-- different order we see a significant performance degradation (~2x).+{-# INLINE bindAsync #-}+{-# SPECIALIZE bindAsync :: AsyncT IO a -> (a -> AsyncT IO b) -> AsyncT IO b #-}+bindAsync :: MonadAsync m => AsyncT m a -> (a -> AsyncT m b) -> AsyncT m b+bindAsync m f = fromStream $ K.bindWith async (adapt m) (\a -> adapt $ f a)++-- GHC: if we specify arguments in the definition of (>>=) we see a significant+-- performance degradation (~2x).+instance MonadAsync m => Monad (AsyncT m) where+ return = pure+ (>>=) = bindAsync++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)++------------------------------------------------------------------------------+-- WAsyncT+------------------------------------------------------------------------------++-- | XXX we can implement it more efficienty by directly implementing instead+-- of combining streams using wAsync.+{-# INLINE consMWAsync #-}+{-# SPECIALIZE consMWAsync :: IO a -> WAsyncT IO a -> WAsyncT IO a #-}+consMWAsync :: MonadAsync m => m a -> WAsyncT m a -> WAsyncT m a+consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.+-- Merges two streams concurrently choosing elements from both fairly.+--+-- @since 0.2.0+{-# INLINE wAsync #-}+wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+wAsync = joinStreamVarAsync WAsyncVar++-- | 'WAsyncT' is similar to 'WSerialT' but with concurrent execution.+-- The 'Semigroup' operation (@<>@) for 'WAsyncT' merges two streams+-- concurrently interleaving the actions from both the streams. In @s1+-- <> s2 <> s3 ...@, the individual actions from streams @s1@, @s2@ and @s3@+-- are scheduled for execution in a round-robin fashion. Multiple scheduled+-- actions may be executed concurrently, the results from concurrent executions+-- are consumed in the order in which they become available.+--+--+-- The @W@ in the name stands for @wide@ or breadth wise scheduling in+-- contrast to the depth wise scheduling behavior of 'AsyncT'.+--+-- @+-- import "Streamly"+-- import qualified "Streamly.Prelude" as S+-- import Control.Concurrent+--+-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print+-- @+-- @+-- [1,3,2,4]+-- @+--+-- For this example, we are using @maxThreads 1@ so that concurrent thread+-- scheduling does not affect the results and make them unpredictable. Let's+-- now take a more general example:+--+-- @+-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2,3]) \<> (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])) >>= print+-- @+-- @+-- [1,4,2,7,5,3,8,6,9]+-- @+--+-- This is how the execution of the above stream proceeds:+--+-- 1. The scheduler queue is initialized with @[S.fromList [1,2,3],+-- (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])]@ assuming the head of the+-- queue is represented by the rightmost item.+-- 2. @S.fromList [1,2,3]@ is executed, yielding the element @1@ and putting+-- @[2,3]@ at the back of the scheduler queue. The scheduler queue now looks+-- like @[(S.fromList [4,5,6]) \<> (S.fromList [7,8,9]), S.fromList [2,3]]@.+-- 3. Now @(S.fromList [4,5,6]) \<> (S.fromList [7,8,9])@ is picked up for+-- execution, @S.fromList [7,8,9]@ is added at the back of the queue and+-- @S.fromList [4,5,6]@ is executed, yielding the element @4@ and adding+-- @S.fromList [5,6]@ at the back of the queue. The queue now looks like+-- @[S.fromList [2,3], S.fromList [7,8,9], S.fromList [5,6]]@.+-- 4. Note that the scheduler queue expands by one more stream component in+-- every pass because one more @<>@ is broken down into two components. At this+-- point there are no more @<>@ operations to be broken down further and the+-- queue has reached its maximum size. Now these streams are scheduled in+-- round-robin fashion yielding @[2,7,5,3,8,8,9]@.+--+-- As we see above, in a right associated expression composed with @<>@, only+-- one @<>@ operation is broken down into two components in one execution,+-- therefore, if we have @n@ streams composed using @<>@ it will take @n@+-- scheduler passes to expand the whole expression. By the time @n-th@+-- component is added to the scheduler queue, the first component would have+-- received @n@ scheduler passes.+--+-- Since all streams get interleaved, this operation is not suitable for+-- folding an infinite lazy container of infinite size streams. However, if+-- the streams are small, the streams on the left may get finished before more+-- streams are added to the scheduler queue from the right side of the+-- expression, so it may be possible to fold an infinite lazy container of+-- streams. For example, if the streams are of size @n@ then at most @n@+-- streams would be in the scheduler queue at a time.+--+-- Note that 'WSerialT' and 'WAsyncT' differ in their scheduling behavior,+-- therefore the output of 'WAsyncT' even with a single thread of execution is+-- not the same as that of 'WSerialT' See notes in 'WSerialT' for details about+-- its scheduling behavior.+--+-- Any exceptions generated by a constituent stream are propagated to the+-- output stream. The output and exceptions from a single stream are guaranteed+-- to arrive in the same order in the resulting stream as they were generated+-- in the input stream. However, the relative ordering of elements from+-- different streams in the resulting stream can vary depending on scheduling+-- and generation delays.+--+-- Similarly, the 'Monad' instance of 'WAsyncT' runs /all/ iterations fairly+-- concurrently using a round robin scheduling.+--+-- @+-- main = 'drain' . 'wAsyncly' $ do+-- n <- return 3 \<\> return 2 \<\> return 1+-- S.yieldM $ do+-- threadDelay (n * 1000000)+-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)+-- @+-- @+-- ThreadId 40: Delay 1+-- ThreadId 39: Delay 2+-- ThreadId 38: Delay 3+-- @+--+-- @since 0.2.0+newtype WAsyncT m a = WAsyncT {getWAsyncT :: Stream m a}+ deriving (MonadTrans)++-- | A round robin parallely composing IO stream of elements of type @a@.+-- See 'WAsyncT' documentation for more details.+--+-- @since 0.2.0+type WAsync = WAsyncT IO++-- | Fix the type of a polymorphic stream as 'WAsyncT'.+--+-- @since 0.2.0+wAsyncly :: IsStream t => WAsyncT m a -> t m a+wAsyncly = adapt++instance IsStream WAsyncT where+ toStream = getWAsyncT+ fromStream = WAsyncT+ consM = consMWAsync+ (|:) = consMWAsync++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++{-# INLINE mappendWAsync #-}+{-# SPECIALIZE mappendWAsync :: WAsyncT IO a -> WAsyncT IO a -> WAsyncT IO a #-}+mappendWAsync :: MonadAsync m => WAsyncT m a -> WAsyncT m a -> WAsyncT m a+mappendWAsync m1 m2 = fromStream $ wAsync (toStream m1) (toStream m2)++instance MonadAsync m => Semigroup (WAsyncT m a) where+ (<>) = mappendWAsync++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance MonadAsync m => Monoid (WAsyncT m a) where+ mempty = K.nil+ mappend = (<>)++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++{-# INLINE apWAsync #-}+{-# SPECIALIZE apWAsync :: WAsyncT IO (a -> b) -> WAsyncT IO a -> WAsyncT IO b #-}+apWAsync :: MonadAsync m => WAsyncT m (a -> b) -> WAsyncT m a -> WAsyncT m b+apWAsync (WAsyncT m1) (WAsyncT m2) =+ let f x1 = K.concatMapBy wAsync (pure . x1) m2+ in WAsyncT $ K.concatMapBy wAsync f m1++-- GHC: if we specify arguments in the definition of (<*>) we see a significant+-- performance degradation (~2x).+instance (Monad m, MonadAsync m) => Applicative (WAsyncT m) where+ pure = WAsyncT . K.yield+ (<*>) = apWAsync++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++-- GHC: if we change the implementation of bindWith with arguments in a+-- different order we see a significant performance degradation (~2x).+{-# INLINE bindWAsync #-}+{-# SPECIALIZE bindWAsync :: WAsyncT IO a -> (a -> WAsyncT IO b) -> WAsyncT IO b #-}+bindWAsync :: MonadAsync m => WAsyncT m a -> (a -> WAsyncT m b) -> WAsyncT m b+bindWAsync m f = fromStream $ K.bindWith wAsync (adapt m) (\a -> adapt $ f a)++-- GHC: if we specify arguments in the definition of (>>=) we see a significant+-- performance degradation (~2x).+instance MonadAsync m => Monad (WAsyncT m) where+ return = pure+ (>>=) = bindWAsync++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++MONAD_COMMON_INSTANCES(WAsyncT, MONADPARALLEL)
+ src/Streamly/Internal/Data/Stream/Combinators.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE CPP #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.Combinators+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Combinators+ ( maxThreads+ , maxBuffer+ , maxYields+ , rate+ , avgRate+ , minRate+ , maxRate+ , constRate+ , inspectMode+ , printState+ )+where++import Control.Monad.IO.Class (MonadIO(liftIO))+import Data.Int (Int64)++import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.StreamK+import Streamly.Internal.Data.Stream.Serial (SerialT)++-------------------------------------------------------------------------------+-- Concurrency control+-------------------------------------------------------------------------------+--+-- XXX need to write these in direct style otherwise they will break fusion.+--+-- | Specify the maximum number of threads that can be spawned concurrently for+-- any concurrent combinator in a stream.+-- A value of 0 resets the thread limit to default, a negative value means+-- there is no limit. The default value is 1500. 'maxThreads' does not affect+-- 'ParallelT' streams as they can use unbounded number of threads.+--+-- When the actions in a stream are IO bound, having blocking IO calls, this+-- option can be used to control the maximum number of in-flight IO requests.+-- When the actions are CPU bound this option can be used to+-- control the amount of CPU used by the stream.+--+-- @since 0.4.0+{-# INLINE_NORMAL maxThreads #-}+maxThreads :: IsStream t => Int -> t m a -> t m a+maxThreads n m = mkStream $ \st stp sng yld ->+ foldStreamShared (setMaxThreads n st) stp sng yld m++{-+{-# RULES "maxThreadsSerial serial" maxThreads = maxThreadsSerial #-}+maxThreadsSerial :: Int -> SerialT m a -> SerialT m a+maxThreadsSerial _ = id+-}++-- | Specify the maximum size of the buffer for storing the results from+-- concurrent computations. If the buffer becomes full we stop spawning more+-- concurrent tasks until there is space in the buffer.+-- A value of 0 resets the buffer size to default, a negative value means+-- there is no limit. The default value is 1500.+--+-- CAUTION! using an unbounded 'maxBuffer' value (i.e. a negative value)+-- coupled with an unbounded 'maxThreads' value is a recipe for disaster in+-- presence of infinite streams, or very large streams. Especially, it must+-- not be used when 'pure' is used in 'ZipAsyncM' streams as 'pure' in+-- applicative zip streams generates an infinite stream causing unbounded+-- concurrent generation with no limit on the buffer or threads.+--+-- @since 0.4.0+{-# INLINE_NORMAL maxBuffer #-}+maxBuffer :: IsStream t => Int -> t m a -> t m a+maxBuffer n m = mkStream $ \st stp sng yld ->+ foldStreamShared (setMaxBuffer n st) stp sng yld m++{-+{-# RULES "maxBuffer serial" maxBuffer = maxBufferSerial #-}+maxBufferSerial :: Int -> SerialT m a -> SerialT m a+maxBufferSerial _ = id+-}++-- | Specify the pull rate of a stream.+-- A 'Nothing' value resets the rate to default which is unlimited. When the+-- rate is specified, concurrent production may be ramped up or down+-- automatically to achieve the specified yield rate. The specific behavior for+-- different styles of 'Rate' specifications is documented under 'Rate'. The+-- effective maximum production rate achieved by a stream is governed by:+--+-- * The 'maxThreads' limit+-- * The 'maxBuffer' limit+-- * The maximum rate that the stream producer can achieve+-- * The maximum rate that the stream consumer can achieve+--+-- @since 0.5.0+{-# INLINE_NORMAL rate #-}+rate :: IsStream t => Maybe Rate -> t m a -> t m a+rate r m = mkStream $ \st stp sng yld ->+ case r of+ Just (Rate low goal _ _) | goal < low ->+ error "rate: Target rate cannot be lower than minimum rate."+ Just (Rate _ goal high _) | goal > high ->+ error "rate: Target rate cannot be greater than maximum rate."+ Just (Rate low _ high _) | low > high ->+ error "rate: Minimum rate cannot be greater than maximum rate."+ _ -> foldStreamShared (setStreamRate r st) stp sng yld m++-- XXX implement for serial streams as well, as a simple delay++{-+{-# RULES "rate serial" rate = yieldRateSerial #-}+yieldRateSerial :: Double -> SerialT m a -> SerialT m a+yieldRateSerial _ = id+-}++-- | Same as @rate (Just $ Rate (r/2) r (2*r) maxBound)@+--+-- Specifies the average production rate of a stream in number of yields+-- per second (i.e. @Hertz@). Concurrent production is ramped up or down+-- automatically to achieve the specified average yield rate. The rate can+-- go down to half of the specified rate on the lower side and double of+-- the specified rate on the higher side.+--+-- @since 0.5.0+avgRate :: IsStream t => Double -> t m a -> t m a+avgRate r = rate (Just $ Rate (r/2) r (2*r) maxBound)++-- | Same as @rate (Just $ Rate r r (2*r) maxBound)@+--+-- Specifies the minimum rate at which the stream should yield values. As+-- far as possible the yield rate would never be allowed to go below the+-- specified rate, even though it may possibly go above it at times, the+-- upper limit is double of the specified rate.+--+-- @since 0.5.0+minRate :: IsStream t => Double -> t m a -> t m a+minRate r = rate (Just $ Rate r r (2*r) maxBound)++-- | Same as @rate (Just $ Rate (r/2) r r maxBound)@+--+-- Specifies the maximum rate at which the stream should yield values. As+-- far as possible the yield rate would never be allowed to go above the+-- specified rate, even though it may possibly go below it at times, the+-- lower limit is half of the specified rate. This can be useful in+-- applications where certain resource usage must not be allowed to go+-- beyond certain limits.+--+-- @since 0.5.0+maxRate :: IsStream t => Double -> t m a -> t m a+maxRate r = rate (Just $ Rate (r/2) r r maxBound)++-- | Same as @rate (Just $ Rate r r r 0)@+--+-- Specifies a constant yield rate. If for some reason the actual rate+-- goes above or below the specified rate we do not try to recover it by+-- increasing or decreasing the rate in future. This can be useful in+-- applications like graphics frame refresh where we need to maintain a+-- constant refresh rate.+--+-- @since 0.5.0+constRate :: IsStream t => Double -> t m a -> t m a+constRate r = rate (Just $ Rate r r r 0)++-- | Specify the average latency, in nanoseconds, of a single threaded action+-- in a concurrent composition. Streamly can measure the latencies, but that is+-- possible only after at least one task has completed. This combinator can be+-- used to provide a latency hint so that rate control using 'rate' can take+-- that into account right from the beginning. When not specified then a+-- default behavior is chosen which could be too slow or too fast, and would be+-- restricted by any other control parameters configured.+-- A value of 0 indicates default behavior, a negative value means there is no+-- limit i.e. zero latency.+-- This would normally be useful only in high latency and high throughput+-- cases.+--+{-# INLINE_NORMAL _serialLatency #-}+_serialLatency :: IsStream t => Int -> t m a -> t m a+_serialLatency n m = mkStream $ \st stp sng yld ->+ foldStreamShared (setStreamLatency n st) stp sng yld m++{-+{-# RULES "serialLatency serial" _serialLatency = serialLatencySerial #-}+serialLatencySerial :: Int -> SerialT m a -> SerialT m a+serialLatencySerial _ = id+-}++-- Stop concurrent dispatches after this limit. This is useful in API's like+-- "take" where we want to dispatch only upto the number of elements "take"+-- needs. This value applies only to the immediate next level and is not+-- inherited by everything in enclosed scope.+{-# INLINE_NORMAL maxYields #-}+maxYields :: IsStream t => Maybe Int64 -> t m a -> t m a+maxYields n m = mkStream $ \st stp sng yld ->+ foldStreamShared (setYieldLimit n st) stp sng yld m++{-# RULES "maxYields serial" maxYields = maxYieldsSerial #-}+maxYieldsSerial :: Maybe Int64 -> SerialT m a -> SerialT m a+maxYieldsSerial _ = id++printState :: MonadIO m => State Stream m a -> m ()+printState st = liftIO $ do+ let msv = streamVar st+ case msv of+ Just sv -> dumpSVar sv >>= putStrLn+ Nothing -> putStrLn "No SVar"++-- | Print debug information about an SVar when the stream ends+inspectMode :: IsStream t => t m a -> t m a+inspectMode m = mkStream $ \st stp sng yld ->+ foldStreamShared (setInspectMode st) stp sng yld m
+ src/Streamly/Internal/Data/Stream/Enumeration.hs view
@@ -0,0 +1,550 @@+{-# LANGUAGE CPP #-}++-- |+-- Module : Streamly.Internal.Data.Stream.Enumeration+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- The functions defined in this module should be rarely needed for direct use,+-- try to use the operations from the 'Enumerable' type class+-- instances instead.+--+-- This module provides an 'Enumerable' type class to enumerate 'Enum' types+-- into a stream. The operations in this type class correspond to similar+-- perations in the 'Enum' type class, the only difference is that they produce+-- a stream instead of a list. These operations cannot be defined generically+-- based on the 'Enum' type class. We provide instances for commonly used+-- types. If instances for other types are needed convenience functions defined+-- in this module can be used to define them. Alternatively, these functions+-- can be used directly.++module Streamly.Internal.Data.Stream.Enumeration+ (+ Enumerable (..)++ -- ** Enumerating 'Bounded' 'Enum' Types+ , enumerate+ , enumerateTo+ , enumerateFromBounded++ -- ** Enumerating 'Enum' Types not larger than 'Int'+ , enumerateFromToSmall+ , enumerateFromThenToSmall+ , enumerateFromThenSmallBounded++ -- ** Enumerating 'Bounded' 'Integral' Types+ , enumerateFromIntegral+ , enumerateFromThenIntegral++ -- ** Enumerating 'Integral' Types+ , enumerateFromToIntegral+ , enumerateFromThenToIntegral++ -- ** Enumerating unbounded 'Integral' Types+ , enumerateFromStepIntegral++ -- ** Enumerating 'Fractional' Types+ , enumerateFromFractional+ , enumerateFromToFractional+ , enumerateFromThenFractional+ , enumerateFromThenToFractional+ )+where++import Data.Fixed+import Data.Int+import Data.Ratio+import Data.Word+import Numeric.Natural+import Data.Functor.Identity (Identity(..))++import Streamly.Internal.Data.Stream.StreamD (fromStreamD)+import Streamly.Internal.Data.Stream.StreamK (IsStream(..))++import qualified Streamly.Internal.Data.Stream.StreamD as D+import qualified Streamly.Internal.Data.Stream.Serial as Serial++-------------------------------------------------------------------------------+-- Enumeration of Integral types+-------------------------------------------------------------------------------+--+-- | @enumerateFromStepIntegral from step@ generates an infinite stream whose+-- first element is @from@ and the successive elements are in increments of+-- @step@.+--+-- CAUTION: This function is not safe for finite integral types. It does not+-- check for overflow, underflow or bounds.+--+-- @+-- > S.toList $ S.take 4 $ S.enumerateFromStepIntegral 0 2+-- [0,2,4,6]+-- > S.toList $ S.take 3 $ S.enumerateFromStepIntegral 0 (-2)+-- [0,-2,-4]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromStepIntegral #-}+enumerateFromStepIntegral+ :: (IsStream t, Monad m, Integral a)+ => a -> a -> t m a+enumerateFromStepIntegral from stride =+ fromStreamD $ D.enumerateFromStepIntegral from stride++-- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a+-- stream whose first element is @from@ and the successive elements are in+-- increments of @1@. The stream is bounded by the size of the 'Integral' type.+--+-- @+-- > S.toList $ S.take 4 $ S.enumerateFromIntegral (0 :: Int)+-- [0,1,2,3]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromIntegral #-}+enumerateFromIntegral+ :: (IsStream t, Monad m, Integral a, Bounded a)+ => a -> t m a+enumerateFromIntegral from = fromStreamD $ D.enumerateFromIntegral from++-- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from+-- then@ generates a stream whose first element is @from@, the second element+-- is @then@ and the successive elements are in increments of @then - from@.+-- The stream is bounded by the size of the 'Integral' type.+--+-- @+-- > S.toList $ S.take 4 $ S.enumerateFromThenIntegral (0 :: Int) 2+-- [0,2,4,6]+-- > S.toList $ S.take 4 $ S.enumerateFromThenIntegral (0 :: Int) (-2)+-- [0,-2,-4,-6]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromThenIntegral #-}+enumerateFromThenIntegral+ :: (IsStream t, Monad m, Integral a, Bounded a)+ => a -> a -> t m a+enumerateFromThenIntegral from next =+ fromStreamD $ D.enumerateFromThenIntegral from next++-- | Enumerate an 'Integral' type up to a given limit.+-- @enumerateFromToIntegral from to@ generates a finite stream whose first+-- element is @from@ and successive elements are in increments of @1@ up to+-- @to@.+--+-- @+-- > S.toList $ S.enumerateFromToIntegral 0 4+-- [0,1,2,3,4]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromToIntegral #-}+enumerateFromToIntegral :: (IsStream t, Monad m, Integral a) => a -> a -> t m a+enumerateFromToIntegral from to =+ fromStreamD $ D.enumerateFromToIntegral from to++-- | Enumerate an 'Integral' type in steps up to a given limit.+-- @enumerateFromThenToIntegral from then to@ generates a finite stream whose+-- first element is @from@, the second element is @then@ and the successive+-- elements are in increments of @then - from@ up to @to@.+--+-- @+-- > S.toList $ S.enumerateFromThenToIntegral 0 2 6+-- [0,2,4,6]+-- > S.toList $ S.enumerateFromThenToIntegral 0 (-2) (-6)+-- [0,-2,-4,-6]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromThenToIntegral #-}+enumerateFromThenToIntegral+ :: (IsStream t, Monad m, Integral a)+ => a -> a -> a -> t m a+enumerateFromThenToIntegral from next to =+ fromStreamD $ D.enumerateFromThenToIntegral from next to++-------------------------------------------------------------------------------+-- Enumeration of Fractional types+-------------------------------------------------------------------------------+--+-- Even though the underlying implementation of enumerateFromFractional and+-- enumerateFromThenFractional works for any 'Num' we have restricted these to+-- 'Fractional' because these do not perform any bounds check, in contrast to+-- integral versions and are therefore not equivalent substitutes for those.+--+-- | Numerically stable enumeration from a 'Fractional' number in steps of size+-- @1@. @enumerateFromFractional from@ generates a stream whose first element+-- is @from@ and the successive elements are in increments of @1@. No overflow+-- or underflow checks are performed.+--+-- This is the equivalent to 'enumFrom' for 'Fractional' types. For example:+--+-- @+-- > S.toList $ S.take 4 $ S.enumerateFromFractional 1.1+-- [1.1,2.1,3.1,4.1]+-- @+--+--+-- @since 0.6.0+{-# INLINE enumerateFromFractional #-}+enumerateFromFractional :: (IsStream t, Monad m, Fractional a) => a -> t m a+enumerateFromFractional from = fromStreamD $ D.numFrom from++-- | Numerically stable enumeration from a 'Fractional' number in steps.+-- @enumerateFromThenFractional from then@ generates a stream whose first+-- element is @from@, the second element is @then@ and the successive elements+-- are in increments of @then - from@. No overflow or underflow checks are+-- performed.+--+-- This is the equivalent of 'enumFromThen' for 'Fractional' types. For+-- example:+--+-- @+-- > S.toList $ S.take 4 $ S.enumerateFromThenFractional 1.1 2.1+-- [1.1,2.1,3.1,4.1]+-- > S.toList $ S.take 4 $ S.enumerateFromThenFractional 1.1 (-2.1)+-- [1.1,-2.1,-5.300000000000001,-8.500000000000002]+-- @+--+-- @since 0.6.0+{-# INLINE enumerateFromThenFractional #-}+enumerateFromThenFractional+ :: (IsStream t, Monad m, Fractional a)+ => a -> a -> t m a+enumerateFromThenFractional from next = fromStreamD $ D.numFromThen from next++-- | Numerically stable enumeration from a 'Fractional' number to a given+-- limit. @enumerateFromToFractional from to@ generates a finite stream whose+-- first element is @from@ and successive elements are in increments of @1@ up+-- to @to@.+--+-- This is the equivalent of 'enumFromTo' for 'Fractional' types. For+-- example:+--+-- @+-- > S.toList $ S.enumerateFromToFractional 1.1 4+-- [1.1,2.1,3.1,4.1]+-- > S.toList $ S.enumerateFromToFractional 1.1 4.6+-- [1.1,2.1,3.1,4.1,5.1]+-- @+--+-- Notice that the last element is equal to the specified @to@ value after+-- rounding to the nearest integer.+--+-- @since 0.6.0+{-# INLINE enumerateFromToFractional #-}+enumerateFromToFractional+ :: (IsStream t, Monad m, Fractional a, Ord a)+ => a -> a -> t m a+enumerateFromToFractional from to =+ fromStreamD $ D.enumerateFromToFractional from to++-- | Numerically stable enumeration from a 'Fractional' number in steps up to a+-- given limit. @enumerateFromThenToFractional from then to@ generates a+-- finite stream whose first element is @from@, the second element is @then@+-- and the successive elements are in increments of @then - from@ up to @to@.+--+-- This is the equivalent of 'enumFromThenTo' for 'Fractional' types. For+-- example:+--+-- @+-- > S.toList $ S.enumerateFromThenToFractional 0.1 2 6+-- [0.1,2.0,3.9,5.799999999999999]+-- > S.toList $ S.enumerateFromThenToFractional 0.1 (-2) (-6)+-- [0.1,-2.0,-4.1000000000000005,-6.200000000000001]+-- @+--+--+-- @since 0.6.0+{-# INLINE enumerateFromThenToFractional #-}+enumerateFromThenToFractional+ :: (IsStream t, Monad m, Fractional a, Ord a)+ => a -> a -> a -> t m a+enumerateFromThenToFractional from next to =+ fromStreamD $ D.enumerateFromThenToFractional from next to++-------------------------------------------------------------------------------+-- Enumeration of Enum types not larger than Int+-------------------------------------------------------------------------------+--+-- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'.+--+-- @since 0.6.0+{-# INLINE enumerateFromToSmall #-}+enumerateFromToSmall :: (IsStream t, Monad m, Enum a) => a -> a -> t m a+enumerateFromToSmall from to = Serial.map toEnum $+ enumerateFromToIntegral (fromEnum from) (fromEnum to)++-- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'.+--+-- @since 0.6.0+{-# INLINE enumerateFromThenToSmall #-}+enumerateFromThenToSmall :: (IsStream t, Monad m, Enum a)+ => a -> a -> a -> t m a+enumerateFromThenToSmall from next to = Serial.map toEnum $+ enumerateFromThenToIntegral (fromEnum from) (fromEnum next) (fromEnum to)++-- | 'enumerateFromThen' for 'Enum' types not larger than 'Int'.+--+-- Note: We convert the 'Enum' to 'Int' and enumerate the 'Int'. If a+-- type is bounded but does not have a 'Bounded' instance then we can go on+-- enumerating it beyond the legal values of the type, resulting in the failure+-- of 'toEnum' when converting back to 'Enum'. Therefore we require a 'Bounded'+-- instance for this function to be safely used.+--+-- @since 0.6.0+{-# INLINE enumerateFromThenSmallBounded #-}+enumerateFromThenSmallBounded :: (IsStream t, Monad m, Enumerable a, Bounded a)+ => a -> a -> t m a+enumerateFromThenSmallBounded from next =+ case fromEnum next >= fromEnum from of+ True -> enumerateFromThenTo from next maxBound+ False -> enumerateFromThenTo from next minBound++-------------------------------------------------------------------------------+-- Enumerable type class+-------------------------------------------------------------------------------+--+-- NOTE: We would like to rewrite calls to fromList [1..] etc. to stream+-- enumerations like this:+--+-- {-# RULES "fromList enumFrom" [1]+-- forall (a :: Int). D.fromList (enumFrom a) = D.enumerateFromIntegral a #-}+--+-- But this does not work because enumFrom is a class method and GHC rewrites+-- it quickly, so we do not get a chance to have our rule fired.++-- | Types that can be enumerated as a stream. The operations in this type+-- class are equivalent to those in the 'Enum' type class, except that these+-- generate a stream instead of a list. Use the functions in+-- "Streamly.Internal.Data.Stream.Enumeration" module to define new instances.+--+-- @since 0.6.0+class Enum a => Enumerable a where+ -- | @enumerateFrom from@ generates a stream starting with the element+ -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or+ -- generating an infinite stream when the type is not 'Bounded'.+ --+ -- @+ -- > S.toList $ S.take 4 $ S.enumerateFrom (0 :: Int)+ -- [0,1,2,3]+ -- @+ --+ -- For 'Fractional' types, enumeration is numerically stable. However, no+ -- overflow or underflow checks are performed.+ --+ -- @+ -- > S.toList $ S.take 4 $ S.enumerateFrom 1.1+ -- [1.1,2.1,3.1,4.1]+ -- @+ --+ -- @since 0.6.0+ enumerateFrom :: (IsStream t, Monad m) => a -> t m a++ -- | Generate a finite stream starting with the element @from@, enumerating+ -- the type up to the value @to@. If @to@ is smaller than @from@ then an+ -- empty stream is returned.+ --+ -- @+ -- > S.toList $ S.enumerateFromTo 0 4+ -- [0,1,2,3,4]+ -- @+ --+ -- For 'Fractional' types, the last element is equal to the specified @to@+ -- value after rounding to the nearest integral value.+ --+ -- @+ -- > S.toList $ S.enumerateFromTo 1.1 4+ -- [1.1,2.1,3.1,4.1]+ -- > S.toList $ S.enumerateFromTo 1.1 4.6+ -- [1.1,2.1,3.1,4.1,5.1]+ -- @+ --+ -- @since 0.6.0+ enumerateFromTo :: (IsStream t, Monad m) => a -> a -> t m a++ -- | @enumerateFromThen from then@ generates a stream whose first element+ -- is @from@, the second element is @then@ and the successive elements are+ -- in increments of @then - from@. Enumeration can occur downwards or+ -- upwards depending on whether @then@ comes before or after @from@. For+ -- 'Bounded' types the stream ends when 'maxBound' is reached, for+ -- unbounded types it keeps enumerating infinitely.+ --+ -- @+ -- > S.toList $ S.take 4 $ S.enumerateFromThen 0 2+ -- [0,2,4,6]+ -- > S.toList $ S.take 4 $ S.enumerateFromThen 0 (-2)+ -- [0,-2,-4,-6]+ -- @+ --+ -- @since 0.6.0+ enumerateFromThen :: (IsStream t, Monad m) => a -> a -> t m a++ -- | @enumerateFromThenTo from then to@ generates a finite stream whose+ -- first element is @from@, the second element is @then@ and the successive+ -- elements are in increments of @then - from@ up to @to@. Enumeration can+ -- occur downwards or upwards depending on whether @then@ comes before or+ -- after @from@.+ --+ -- @+ -- > S.toList $ S.enumerateFromThenTo 0 2 6+ -- [0,2,4,6]+ -- > S.toList $ S.enumerateFromThenTo 0 (-2) (-6)+ -- [0,-2,-4,-6]+ -- @+ --+ -- @since 0.6.0+ enumerateFromThenTo :: (IsStream t, Monad m) => a -> a -> a -> t m a++-- MAYBE: Sometimes it is more convenient to know the count rather then the+-- ending or starting element. For those cases we can define the folllowing+-- APIs. All of these will work only for bounded types if we represent the+-- count by Int.+--+-- enumerateN+-- enumerateFromN+-- enumerateToN+-- enumerateFromStep+-- enumerateFromStepN++-------------------------------------------------------------------------------+-- Convenient functions for bounded types+-------------------------------------------------------------------------------+--+-- |+-- > enumerate = enumerateFrom minBound+--+-- Enumerate a 'Bounded' type from its 'minBound' to 'maxBound'+--+-- @since 0.6.0+{-# INLINE enumerate #-}+enumerate :: (IsStream t, Monad m, Bounded a, Enumerable a) => t m a+enumerate = enumerateFrom minBound++-- |+-- > enumerateTo = enumerateFromTo minBound+--+-- Enumerate a 'Bounded' type from its 'minBound' to specified value.+--+-- @since 0.6.0+{-# INLINE enumerateTo #-}+enumerateTo :: (IsStream t, Monad m, Bounded a, Enumerable a) => a -> t m a+enumerateTo = enumerateFromTo minBound++-- |+-- > enumerateFromBounded = enumerateFromTo from maxBound+--+-- 'enumerateFrom' for 'Bounded' 'Enum' types.+--+-- @since 0.6.0+{-# INLINE enumerateFromBounded #-}+enumerateFromBounded :: (IsStream t, Monad m, Enumerable a, Bounded a)+ => a -> t m a+enumerateFromBounded from = enumerateFromTo from maxBound++-------------------------------------------------------------------------------+-- Enumerable Instances+-------------------------------------------------------------------------------+--+-- For Enum types smaller than or equal to Int size.+#define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \+instance Enumerable SMALL_TYPE where { \+ {-# INLINE enumerateFrom #-}; \+ enumerateFrom = enumerateFromBounded; \+ {-# INLINE enumerateFromThen #-}; \+ enumerateFromThen = enumerateFromThenSmallBounded; \+ {-# INLINE enumerateFromTo #-}; \+ enumerateFromTo = enumerateFromToSmall; \+ {-# INLINE enumerateFromThenTo #-}; \+ enumerateFromThenTo = enumerateFromThenToSmall }+++ENUMERABLE_BOUNDED_SMALL(())+ENUMERABLE_BOUNDED_SMALL(Bool)+ENUMERABLE_BOUNDED_SMALL(Ordering)+ENUMERABLE_BOUNDED_SMALL(Char)++-- For bounded Integral Enum types, may be larger than Int.+#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \+instance Enumerable INTEGRAL_TYPE where { \+ {-# INLINE enumerateFrom #-}; \+ enumerateFrom = enumerateFromIntegral; \+ {-# INLINE enumerateFromThen #-}; \+ enumerateFromThen = enumerateFromThenIntegral; \+ {-# INLINE enumerateFromTo #-}; \+ enumerateFromTo = enumerateFromToIntegral; \+ {-# INLINE enumerateFromThenTo #-}; \+ enumerateFromThenTo = enumerateFromThenToIntegral }++ENUMERABLE_BOUNDED_INTEGRAL(Int)+ENUMERABLE_BOUNDED_INTEGRAL(Int8)+ENUMERABLE_BOUNDED_INTEGRAL(Int16)+ENUMERABLE_BOUNDED_INTEGRAL(Int32)+ENUMERABLE_BOUNDED_INTEGRAL(Int64)+ENUMERABLE_BOUNDED_INTEGRAL(Word)+ENUMERABLE_BOUNDED_INTEGRAL(Word8)+ENUMERABLE_BOUNDED_INTEGRAL(Word16)+ENUMERABLE_BOUNDED_INTEGRAL(Word32)+ENUMERABLE_BOUNDED_INTEGRAL(Word64)++-- For unbounded Integral Enum types.+#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \+instance Enumerable INTEGRAL_TYPE where { \+ {-# INLINE enumerateFrom #-}; \+ enumerateFrom from = enumerateFromStepIntegral from 1; \+ {-# INLINE enumerateFromThen #-}; \+ enumerateFromThen from next = \+ enumerateFromStepIntegral from (next - from); \+ {-# INLINE enumerateFromTo #-}; \+ enumerateFromTo = enumerateFromToIntegral; \+ {-# INLINE enumerateFromThenTo #-}; \+ enumerateFromThenTo = enumerateFromThenToIntegral }++ENUMERABLE_UNBOUNDED_INTEGRAL(Integer)+ENUMERABLE_UNBOUNDED_INTEGRAL(Natural)++#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \+instance (CONSTRAINT) => Enumerable (FRACTIONAL_TYPE) where { \+ {-# INLINE enumerateFrom #-}; \+ enumerateFrom = enumerateFromFractional; \+ {-# INLINE enumerateFromThen #-}; \+ enumerateFromThen = enumerateFromThenFractional; \+ {-# INLINE enumerateFromTo #-}; \+ enumerateFromTo = enumerateFromToFractional; \+ {-# INLINE enumerateFromThenTo #-}; \+ enumerateFromThenTo = enumerateFromThenToFractional }++ENUMERABLE_FRACTIONAL(Float,)+ENUMERABLE_FRACTIONAL(Double,)+ENUMERABLE_FRACTIONAL(Fixed a,HasResolution a)+ENUMERABLE_FRACTIONAL(Ratio a,Integral a)++#if __GLASGOW_HASKELL__ >= 800+instance Enumerable a => Enumerable (Identity a) where+ {-# INLINE enumerateFrom #-}+ enumerateFrom (Identity from) = Serial.map Identity $+ enumerateFrom from+ {-# INLINE enumerateFromThen #-}+ enumerateFromThen (Identity from) (Identity next) = Serial.map Identity $+ enumerateFromThen from next+ {-# INLINE enumerateFromTo #-}+ enumerateFromTo (Identity from) (Identity to) = Serial.map Identity $+ enumerateFromTo from to+ {-# INLINE enumerateFromThenTo #-}+ enumerateFromThenTo (Identity from) (Identity next) (Identity to) =+ Serial.map Identity $ enumerateFromThenTo from next to+#endif++-- TODO+{-+instance Enumerable a => Enumerable (Last a)+instance Enumerable a => Enumerable (First a)+instance Enumerable a => Enumerable (Max a)+instance Enumerable a => Enumerable (Min a)+instance Enumerable a => Enumerable (Const a b)+instance Enumerable (f a) => Enumerable (Alt f a)+instance Enumerable (f a) => Enumerable (Ap f a)+-}
+ src/Streamly/Internal/Data/Stream/Instances.hs view
@@ -0,0 +1,168 @@+------------------------------------------------------------------------------+-- CPP macros for common instances+------------------------------------------------------------------------------++-- XXX use template haskell instead and include Monoid and IsStream instances+-- as well.++#define MONADPARALLEL , MonadAsync m++#define MONAD_COMMON_INSTANCES(STREAM,CONSTRAINT) \+instance Monad m => Functor (STREAM m) where { \+ {-# INLINE fmap #-}; \+ fmap f (STREAM m) = D.fromStreamD $ D.mapM (return . f) $ D.toStreamD m }; \+ \+instance (MonadBase b m, Monad m CONSTRAINT) => MonadBase b (STREAM m) where {\+ liftBase = liftBaseDefault }; \+ \+instance (MonadIO m CONSTRAINT) => MonadIO (STREAM m) where { \+ liftIO = lift . liftIO }; \+ \+instance (MonadThrow m CONSTRAINT) => MonadThrow (STREAM m) where { \+ throwM = lift . throwM }; \+ \+{- \+instance (MonadError e m CONSTRAINT) => MonadError e (STREAM m) where { \+ throwError = lift . throwError; \+ catchError m h = \+ fromStream $ withCatchError (toStream m) (\e -> toStream $ h e) }; \+-} \+ \+instance (MonadReader r m CONSTRAINT) => MonadReader r (STREAM m) where { \+ ask = lift ask; \+ local f m = fromStream $ K.withLocal f (toStream m) }; \+ \+instance (MonadState s m CONSTRAINT) => MonadState s (STREAM m) where { \+ get = lift get; \+ put x = lift (put x); \+ state k = lift (state k) }++------------------------------------------------------------------------------+-- Lists+------------------------------------------------------------------------------++-- Serial streams can act like regular lists using the Identity monad++-- XXX Show instance is 10x slower compared to read, we can do much better.+-- The list show instance itself is really slow.++-- XXX The default definitions of "<" in the Ord instance etc. do not perform+-- well, because they do not get inlined. Need to add INLINE in Ord class in+-- base?++#if MIN_VERSION_deepseq(1,4,3)+#define NFDATA1_INSTANCE(STREAM) \+instance NFData1 (STREAM Identity) where { \+ {-# INLINE liftRnf #-}; \+ liftRnf r = runIdentity . P.foldl' (\_ x -> r x) () }+#else+#define NFDATA1_INSTANCE(STREAM)+#endif++#define LIST_INSTANCES(STREAM) \+instance IsList (STREAM Identity a) where { \+ type (Item (STREAM Identity a)) = a; \+ {-# INLINE fromList #-}; \+ fromList = P.fromList; \+ {-# INLINE toList #-}; \+ toList = runIdentity . P.toList }; \+ \+instance Eq a => Eq (STREAM Identity a) where { \+ {-# INLINE (==) #-}; \+ (==) xs ys = runIdentity $ P.eqBy (==) xs ys }; \+ \+instance Ord a => Ord (STREAM Identity a) where { \+ {-# INLINE compare #-}; \+ compare xs ys = runIdentity $ P.cmpBy compare xs ys; \+ {-# INLINE (<) #-}; \+ x < y = case compare x y of { LT -> True; _ -> False }; \+ {-# INLINE (<=) #-}; \+ x <= y = case compare x y of { GT -> False; _ -> True }; \+ {-# INLINE (>) #-}; \+ x > y = case compare x y of { GT -> True; _ -> False }; \+ {-# INLINE (>=) #-}; \+ x >= y = case compare x y of { LT -> False; _ -> True }; \+ {-# INLINE max #-}; \+ max x y = if x <= y then y else x; \+ {-# INLINE min #-}; \+ min x y = if x <= y then x else y; }; \+ \+instance Show a => Show (STREAM Identity a) where { \+ showsPrec p dl = showParen (p > 10) $ \+ showString "fromList " . shows (toList dl) }; \+ \+instance Read a => Read (STREAM Identity a) where { \+ readPrec = parens $ prec 10 $ do { \+ Ident "fromList" <- lexP; \+ fromList <$> readPrec }; \+ readListPrec = readListPrecDefault }; \+ \+instance (a ~ Char) => IsString (STREAM Identity a) where { \+ {-# INLINE fromString #-}; \+ fromString = P.fromList }; \+ \+instance NFData a => NFData (STREAM Identity a) where { \+ {-# INLINE rnf #-}; \+ rnf = runIdentity . P.foldl' (\_ x -> rnf x) () }; \++-------------------------------------------------------------------------------+-- Foldable+-------------------------------------------------------------------------------++-- The default Foldable instance has several issues:+-- 1) several definitions do not have INLINE on them, so we provide+-- re-implementations with INLINE pragmas.+-- 2) the definitions of sum/product/maximum/minimum are inefficient as they+-- use right folds, they cannot run in constant memory. We provide+-- implementations using strict left folds here.++#define FOLDABLE_INSTANCE(STREAM) \+instance (Foldable m, Monad m) => Foldable (STREAM m) where { \+ \+ {-# INLINE foldMap #-}; \+ foldMap f = fold . P.foldr (mappend . f) mempty; \+ \+ {-# INLINE foldr #-}; \+ foldr f z t = appEndo (foldMap (Endo #. f) t) z; \+ \+ {-# INLINE foldl' #-}; \+ foldl' f z0 xs = foldr f' id xs z0 \+ where { f' x k z = k $! f z x}; \+ \+ {-# INLINE length #-}; \+ length = foldl' (\n _ -> n + 1) 0; \+ \+ {-# INLINE elem #-}; \+ elem = any . (==); \+ \+ {-# INLINE maximum #-}; \+ maximum = \+ fromMaybe (errorWithoutStackTrace $ "maximum: empty stream") \+ . toMaybe \+ . foldl' getMax Nothing' where { \+ getMax Nothing' x = Just' x; \+ getMax (Just' mx) x = Just' $! max mx x }; \+ \+ {-# INLINE minimum #-}; \+ minimum = \+ fromMaybe (errorWithoutStackTrace $ "minimum: empty stream") \+ . toMaybe \+ . foldl' getMin Nothing' where { \+ getMin Nothing' x = Just' x; \+ getMin (Just' mn) x = Just' $! min mn x }; \+ \+ {-# INLINE sum #-}; \+ sum = foldl' (+) 0; \+ \+ {-# INLINE product #-}; \+ product = foldl' (*) 1 }++-------------------------------------------------------------------------------+-- Traversable+-------------------------------------------------------------------------------++#define TRAVERSABLE_INSTANCE(STREAM) \+instance Traversable (STREAM Identity) where { \+ {-# INLINE traverse #-}; \+ traverse f s = runIdentity $ P.foldr consA (pure mempty) s \+ where { consA x ys = liftA2 K.cons (f x) ys }}
+ src/Streamly/Internal/Data/Stream/Parallel.hs view
@@ -0,0 +1,543 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving#-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.Parallel+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Parallel+ (+ -- * Parallel Stream Type+ ParallelT+ , Parallel+ , parallely++ -- * Merge Concurrently+ , parallel+ , parallelFst+ , parallelMin++ -- * Evaluate Concurrently+ , mkParallel++ -- * Tap Concurrently+ , tapAsync+ , distributeAsync_+ )+where++import Control.Concurrent (myThreadId, takeMVar)+import Control.Monad (when)+import Control.Monad.Base (MonadBase(..), liftBaseDefault)+import Control.Monad.Catch (MonadThrow, throwM)+-- import Control.Monad.Error.Class (MonadError(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader.Class (MonadReader(..))+import Control.Monad.State.Class (MonadState(..))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Data.Functor (void)+import Data.IORef (readIORef, writeIORef)+import Data.Maybe (fromJust)+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif+import Prelude hiding (map)++import qualified Data.Set as Set++import Streamly.Internal.Data.Stream.SVar+ (fromSVar, fromProducer, fromConsumer, pushToFold)+import Streamly.Internal.Data.Stream.StreamK+ (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)++import Streamly.Internal.Data.SVar++import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++#include "Instances.hs"++-------------------------------------------------------------------------------+-- Parallel+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- StreamK based worker routines+-------------------------------------------------------------------------------++{-# NOINLINE runOne #-}+runOne+ :: MonadIO m+ => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()+runOne st m0 winfo =+ case getYieldLimit st of+ Nothing -> go m0+ Just _ -> runOneLimited st m0 winfo++ where++ go m = do+ liftIO $ decrementBufferLimit sv+ foldStreamShared st yieldk single stop m++ sv = fromJust $ streamVar st++ stop = liftIO $ do+ incrementBufferLimit sv+ sendStop sv winfo+ sendit a = liftIO $ void $ send sv (ChildYield a)+ single a = sendit a >> (liftIO $ sendStop sv winfo)+ yieldk a r = sendit a >> go r++runOneLimited+ :: MonadIO m+ => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()+runOneLimited st m0 winfo = go m0++ where++ go m = do+ yieldLimitOk <- liftIO $ decrementYieldLimit sv+ if yieldLimitOk+ then do+ liftIO $ decrementBufferLimit sv+ foldStreamShared st yieldk single stop m+ else do+ liftIO $ cleanupSVarFromWorker sv+ liftIO $ sendStop sv winfo++ sv = fromJust $ streamVar st++ stop = liftIO $ do+ incrementBufferLimit sv+ incrementYieldLimit sv+ sendStop sv winfo+ sendit a = liftIO $ void $ send sv (ChildYield a)+ single a = sendit a >> (liftIO $ sendStop sv winfo)+ yieldk a r = sendit a >> go r++-------------------------------------------------------------------------------+-- Consing and appending a stream in parallel style+-------------------------------------------------------------------------------++-- Note that consing and appending requires StreamK as it would not scale well+-- with StreamD unless we are only consing a very small number of streams or+-- elements in a stream. StreamK allows us to manipulate control flow in a way+-- which StreamD cannot allow. StreamK can make a jump without having to+-- remember the past state.++{-# NOINLINE forkSVarPar #-}+forkSVarPar :: (IsStream t, MonadAsync m)+ => SVarStopStyle -> t m a -> t m a -> t m a+forkSVarPar ss m r = mkStream $ \st yld sng stp -> do+ sv <- newParallelVar ss st+ pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)+ case ss of+ StopBy -> liftIO $ do+ set <- readIORef (workerThreads sv)+ writeIORef (svarStopBy sv) $ Set.elemAt 0 set+ _ -> return ()+ pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)+ foldStream st yld sng stp (fromSVar sv)++{-# INLINE joinStreamVarPar #-}+joinStreamVarPar :: (IsStream t, MonadAsync m)+ => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a+joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->+ case streamVar st of+ Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do+ -- Here, WE ARE IN THE WORKER/PRODUCER THREAD, we know that because+ -- the SVar exists. We are running under runOne and the output we+ -- produce ultimately will be sent to the SVar by runOne.+ --+ -- If we came here the worker/runOne is evaluating a `parallel`+ -- combinator. In this case, we always fork a new worker for the+ -- first component (m1) in the parallel composition and continue to+ -- evaluate the second component (m2) in the current worker thread.+ --+ -- When m1 is serially composed, the worker would evaluate it+ -- without any further forks and the resulting output is sent to+ -- the SVar and the evaluation terminates. If m1 is a `parallel`+ -- composition of two streams the worker would again recurses here.+ --+ -- Similarly, when m2 is serially composed it gets evaluated here+ -- and the resulting output is sent to the SVar by the runOne+ -- wrapper. When m2 is composed with `parallel` it will again+ -- recurse here and so on until it finally terminates.+ --+ -- When we create a right associated expression using `parallel`,+ -- then m1 would always terminate without further forks or+ -- recursion into this routine, therefore, the worker returns+ -- immediately after evaluating it. And m2 would continue to+ -- fork/recurse, therefore, the current thread always recurses and+ -- forks new workers one after the other. This is a tail recursive+ -- style execution, m2, the recursive expression always executed at+ -- the tail.+ --+ -- When the expression is left associated, the worker spawned would+ -- get the forking/recursing responsibility and then again the+ -- worker spawned by that worker would fork, thus creating layer+ -- over layer of workers and a chain of threads leading to a very+ -- inefficient execution.+ pushWorkerPar sv (runOne st $ toStream m1)+ foldStreamShared st yld sng stp m2+ _ ->+ -- Here WE ARE IN THE CONSUMER THREAD, we create a new SVar, fork+ -- worker threads to execute m1 and m2 and this thread starts+ -- pulling the stream from the SVar.+ foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)++-------------------------------------------------------------------------------+-- User facing APIs+-------------------------------------------------------------------------------++-- | XXX we can implement it more efficienty by directly implementing instead+-- of combining streams using parallel.+{-# INLINE consMParallel #-}+{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}+consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a+consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'+-- Merges two streams concurrently.+--+-- @since 0.2.0+{-# INLINE parallel #-}+parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+parallel = joinStreamVarPar ParallelVar StopNone++-- This is a co-parallel like combinator for streams, where first stream is the+-- main stream and the rest are just supporting it, when the first ends+-- everything ends.+--+-- | Like `parallel` but stops the output as soon as the first stream stops.+--+-- @since 0.7.0+{-# INLINE parallelFst #-}+parallelFst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+parallelFst = joinStreamVarPar ParallelVar StopBy++-- This is a race like combinator for streams.+--+-- | Like `parallel` but stops the output as soon as any of the two streams+-- stops.+--+-- @since 0.7.0+{-# INLINE parallelMin #-}+parallelMin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+parallelMin = joinStreamVarPar ParallelVar StopAny++------------------------------------------------------------------------------+-- Convert a stream to parallel+------------------------------------------------------------------------------++-- | Generate a stream asynchronously to keep it buffered, lazily consume+-- from the buffer.+--+-- /Internal/+--+mkParallel :: (IsStream t, MonadAsync m) => t m a -> t m a+mkParallel m = mkStream $ \st yld sng stp -> do+ sv <- newParallelVar StopNone (adaptState st)+ -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)+ D.toSVarParallel st sv $ D.toStreamD m+ foldStream st yld sng stp $ fromSVar sv++------------------------------------------------------------------------------+-- Clone and distribute a stream in parallel+------------------------------------------------------------------------------++-- Tap a stream and send the elements to the specified SVar in addition to+-- yielding them again.+--+-- XXX this could be written in StreamD style for better efficiency with fusion.+--+{-# INLINE teeToSVar #-}+teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a+teeToSVar svr m = mkStream $ \st yld sng stp -> do+ foldStreamShared st yld sng stp (go False m)++ where++ go False m0 = mkStream $ \st yld _ stp -> do+ let drain = do+ -- In general, a Stop event would come equipped with the result+ -- of the fold. It is not used here but it would be useful in+ -- applicative and distribute.+ done <- fromConsumer svr+ when (not done) $ do+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"+ $ takeMVar (outputDoorBellFromConsumer svr)+ drain++ stopFold = do+ liftIO $ sendStop svr Nothing+ -- drain/wait until a stop event arrives from the fold.+ drain++ stop = stopFold >> stp+ single a = do+ done <- pushToFold svr a+ yld a (go done (K.nilM stopFold))+ yieldk a r = pushToFold svr a >>= \done -> yld a (go done r)+ in foldStreamShared st yieldk single stop m0++ go True m0 = m0++-- In case of folds the roles of worker and parent on an SVar are reversed. The+-- parent stream pushes values to an SVar instead of pulling from it and a+-- worker thread running the fold pulls from the SVar and folds the stream. We+-- keep a separate channel for pushing exceptions in the reverse direction i.e.+-- from the fold to the parent stream.+--+-- Note: If we terminate due to an exception, we do not actively terminate the+-- fold. It gets cleaned up by the GC.++-- | Create an SVar with a fold consumer that will fold any elements sent to it+-- using the supplied fold function.+{-# INLINE newFoldSVar #-}+newFoldSVar :: (IsStream t, MonadAsync m)+ => State Stream m a -> (t m a -> m b) -> m (SVar Stream m a)+newFoldSVar stt f = do+ -- Buffer size for the SVar is derived from the current state+ sv <- newParallelVar StopAny (adaptState stt)++ -- Add the producer thread-id to the SVar.+ liftIO myThreadId >>= modifyThread sv++ void $ doFork (void $ f $ fromStream $ fromProducer sv)+ (svarMrun sv)+ (handleFoldException sv)+ return sv++-- NOTE: In regular pull style streams, the consumer stream is pulling elements+-- from the SVar and we have several workers producing elements and pushing to+-- SVar. In case of folds, we, the parent stream driving the fold, are the+-- stream producing worker, we start an SVar and start pushing to the SVar, the+-- fold on the other side of the SVar is the consumer stream.+--+-- In the pull stream case exceptions are propagated from the producing workers+-- to the consumer stream, the exceptions are propagated on the same channel as+-- the produced stream elements. However, in case of push style folds the+-- current stream itself is the worker and the fold is the consumer, in this+-- case we have to propagate the exceptions from the consumer to the producer.+-- This is reverse of the pull case and we need a reverse direction channel+-- to propagate the exception.+--+-- | Redirect a copy of the stream to a supplied fold and run it concurrently+-- in an independent thread. The fold may buffer some elements. The buffer size+-- is determined by the prevailing 'maxBuffer' setting.+--+-- @+-- Stream m a -> m b+-- |+-- -----stream m a ---------------stream m a-----+--+-- @+--+-- @+-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)+-- 1+-- 2+-- @+--+-- Exceptions from the concurrently running fold are propagated to the current+-- computation. Note that, because of buffering in the fold, exceptions may be+-- delayed and may not correspond to the current element being processed in the+-- parent stream, but we guarantee that before the parent stream stops the tap+-- finishes and all exceptions from it are drained.+--+--+-- Compare with 'tap'.+--+-- @since 0.7.0+{-# INLINE tapAsync #-}+tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a+tapAsync f m = mkStream $ \st yld sng stp -> do+ sv <- newFoldSVar st f+ foldStreamShared st yld sng stp (teeToSVar sv m)++-- | Concurrently distribute a stream to a collection of fold functions,+-- discarding the outputs of the folds.+--+-- >>> S.drain $ distributeAsync_ [S.mapM_ print, S.mapM_ print] (S.enumerateFromTo 1 2)+--+-- @+-- distributeAsync_ = flip (foldr tapAsync)+-- @+--+-- /Internal/+--+{-# INLINE distributeAsync_ #-}+distributeAsync_ :: (Foldable f, IsStream t, MonadAsync m)+ => f (t m a -> m b) -> t m a -> t m a+distributeAsync_ = flip (foldr tapAsync)++------------------------------------------------------------------------------+-- ParallelT+------------------------------------------------------------------------------++-- | Async composition with strict concurrent execution of all streams.+--+-- The 'Semigroup' instance of 'ParallelT' executes both the streams+-- concurrently without any delay or without waiting for the consumer demand+-- and /merges/ the results as they arrive. If the consumer does not consume+-- the results, they are buffered upto a configured maximum, controlled by the+-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will+-- block until there is space in the buffer.+--+-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a+-- round robin fashion. The key difference is that 'WAsyncT' might wait for the+-- consumer demand before it executes the tasks whereas 'ParallelT' starts+-- executing all the tasks immediately without waiting for the consumer demand.+-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does+-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is+-- strict.+--+-- 'ParallelT' is useful for cases when the streams are required to be+-- evaluated simultaneously irrespective of how the consumer consumes them e.g.+-- when we want to race two tasks and want to start both strictly at the same+-- time or if we have timers in the parallel tasks and our results depend on+-- the timers being started at the same time. If we do not have such+-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more+-- efficient than 'ParallelT'.+--+-- @+-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print+-- @+-- @+-- [1,3,2,4]+-- @+--+-- When streams with more than one element are merged, it yields whichever+-- stream yields first without any bias, unlike the 'Async' style streams.+--+-- Any exceptions generated by a constituent stream are propagated to the+-- output stream. The output and exceptions from a single stream are guaranteed+-- to arrive in the same order in the resulting stream as they were generated+-- in the input stream. However, the relative ordering of elements from+-- different streams in the resulting stream can vary depending on scheduling+-- and generation delays.+--+-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations+-- of the loop concurrently.+--+-- @+-- import "Streamly"+-- import qualified "Streamly.Prelude" as S+-- import Control.Concurrent+--+-- main = 'drain' . 'parallely' $ do+-- n <- return 3 \<\> return 2 \<\> return 1+-- S.yieldM $ do+-- threadDelay (n * 1000000)+-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)+-- @+-- @+-- ThreadId 40: Delay 1+-- ThreadId 39: Delay 2+-- ThreadId 38: Delay 3+-- @+--+-- Note that parallel composition can only combine a finite number of+-- streams as it needs to retain state for each unfinished stream.+--+-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/+--+-- /Since: 0.1.0/+newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}+ deriving (MonadTrans)++-- | A parallely composing IO stream of elements of type @a@.+-- See 'ParallelT' documentation for more details.+--+-- @since 0.2.0+type Parallel = ParallelT IO++-- | Fix the type of a polymorphic stream as 'ParallelT'.+--+-- @since 0.1.0+parallely :: IsStream t => ParallelT m a -> t m a+parallely = adapt++instance IsStream ParallelT where+ toStream = getParallelT+ fromStream = ParallelT++ {-# INLINE consM #-}+ {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}+ consM = consMParallel++ {-# INLINE (|:) #-}+ {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}+ (|:) = consM++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++{-# INLINE mappendParallel #-}+{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}+mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a+mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)++instance MonadAsync m => Semigroup (ParallelT m a) where+ (<>) = mappendParallel++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance MonadAsync m => Monoid (ParallelT m a) where+ mempty = K.nil+ mappend = (<>)++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++{-# INLINE apParallel #-}+{-# SPECIALIZE apParallel :: ParallelT IO (a -> b) -> ParallelT IO a -> ParallelT IO b #-}+apParallel :: MonadAsync m => ParallelT m (a -> b) -> ParallelT m a -> ParallelT m b+apParallel (ParallelT m1) (ParallelT m2) =+ let f x1 = K.concatMapBy parallel (pure . x1) m2+ in ParallelT $ K.concatMapBy parallel f m1++instance (Monad m, MonadAsync m) => Applicative (ParallelT m) where+ {-# INLINE pure #-}+ pure = ParallelT . K.yield+ {-# INLINE (<*>) #-}+ (<*>) = apParallel++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++{-# INLINE bindParallel #-}+{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}+bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b+bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)++instance MonadAsync m => Monad (ParallelT m) where+ return = pure+ (>>=) = bindParallel++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
+ src/Streamly/Internal/Data/Stream/Prelude.hs view
@@ -0,0 +1,339 @@+{-# LANGUAGE CPP #-}++#if __GLASGOW_HASKELL__ >= 800+{-# OPTIONS_GHC -Wno-orphans #-}+#endif++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.Prelude+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Prelude+ (+ -- * Stream Conversion+ fromStreamS+ , toStreamS++ -- * Running Effects+ , drain++ -- * Conversion operations+ , fromList+ , toList++ -- * Fold operations+ , foldrM+ , foldrMx+ , foldr++ , foldlx'+ , foldlMx'+ , foldl'+ , runFold++ -- Lazy left folds are useful only for reversing the stream+ , foldlS+ , foldlT++ , scanlx'+ , scanlMx'+ , postscanlx'+ , postscanlMx'++ -- * Zip style operations+ , eqBy+ , cmpBy++ -- * Foldable instance+ , minimum+ , maximum++ -- * Nesting+ , K.concatMapBy+ , K.concatMap++ -- * Fold Utilities+ , foldWith+ , foldMapWith+ , forEachWith+ )+where++import Control.Monad.Trans (MonadTrans(..))+import Prelude hiding (foldr, minimum, maximum)+import qualified Prelude++import Streamly.Internal.Data.Fold.Types (Fold (..))++#ifdef USE_STREAMK_ONLY+import qualified Streamly.Internal.Data.Stream.StreamK as S+#else+import qualified Streamly.Internal.Data.Stream.StreamD as S+#endif++import Streamly.Internal.Data.Stream.StreamK (IsStream(..))+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++------------------------------------------------------------------------------+-- Conversion to and from direct style stream+------------------------------------------------------------------------------++-- These definitions are dependent on what is imported as S+{-# INLINE fromStreamS #-}+fromStreamS :: (IsStream t, Monad m) => S.Stream m a -> t m a+fromStreamS = fromStream . S.toStreamK++{-# INLINE toStreamS #-}+toStreamS :: (IsStream t, Monad m) => t m a -> S.Stream m a+toStreamS = S.fromStreamK . toStream++------------------------------------------------------------------------------+-- Conversions+------------------------------------------------------------------------------++{-# INLINE_EARLY drain #-}+drain :: (IsStream t, Monad m) => t m a -> m ()+drain m = D.drain $ D.fromStreamK (toStream m)+{-# RULES "drain fallback to CPS" [1]+ forall a. D.drain (D.fromStreamK a) = K.drain a #-}++------------------------------------------------------------------------------+-- Conversions+------------------------------------------------------------------------------++-- |+-- @+-- fromList = 'Prelude.foldr' 'K.cons' 'K.nil'+-- @+--+-- Construct a stream from a list of pure values. This is more efficient than+-- 'K.fromFoldable' for serial streams.+--+-- @since 0.4.0+{-# INLINE_EARLY fromList #-}+fromList :: (Monad m, IsStream t) => [a] -> t m a+fromList = fromStreamS . S.fromList+{-# RULES "fromList fallback to StreamK" [1]+ forall a. S.toStreamK (S.fromList a) = K.fromFoldable a #-}++-- | Convert a stream into a list in the underlying monad.+--+-- @since 0.1.0+{-# INLINE toList #-}+toList :: (Monad m, IsStream t) => t m a -> m [a]+toList m = S.toList $ toStreamS m++------------------------------------------------------------------------------+-- Folds+------------------------------------------------------------------------------++{-# INLINE foldrM #-}+foldrM :: (Monad m, IsStream t) => (a -> m b -> m b) -> m b -> t m a -> m b+foldrM step acc m = S.foldrM step acc $ toStreamS m++{-# INLINE foldrMx #-}+foldrMx :: (Monad m, IsStream t)+ => (a -> m x -> m x) -> m x -> (m x -> m b) -> t m a -> m b+foldrMx step final project m = D.foldrMx step final project $ D.toStreamD m++{-# INLINE foldr #-}+foldr :: (Monad m, IsStream t) => (a -> b -> b) -> b -> t m a -> m b+foldr f z = foldrM (\a b -> b >>= return . f a) (return z)++-- | Like 'foldlx'', but with a monadic step function.+--+-- @since 0.7.0+{-# INLINE foldlMx' #-}+foldlMx' :: (IsStream t, Monad m)+ => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> m b+foldlMx' step begin done m = S.foldlMx' step begin done $ toStreamS m++-- | Strict left fold with an extraction function. Like the standard strict+-- left fold, but applies a user supplied extraction function (the third+-- argument) to the folded value at the end. This is designed to work with the+-- @foldl@ library. The suffix @x@ is a mnemonic for extraction.+--+-- @since 0.7.0+{-# INLINE foldlx' #-}+foldlx' :: (IsStream t, Monad m)+ => (x -> a -> x) -> x -> (x -> b) -> t m a -> m b+foldlx' step begin done m = S.foldlx' step begin done $ toStreamS m++-- | Strict left associative fold.+--+-- @since 0.2.0+{-# INLINE foldl' #-}+foldl' :: (Monad m, IsStream t) => (b -> a -> b) -> b -> t m a -> m b+foldl' step begin m = S.foldl' step begin $ toStreamS m++{-# INLINE foldlS #-}+foldlS :: IsStream t => (t m b -> a -> t m b) -> t m b -> t m a -> t m b+foldlS = K.foldlS++-- | Lazy left fold to a transformer monad.+--+-- For example, to reverse a stream:+--+-- > S.toList $ S.foldlT (flip S.cons) S.nil $ (S.fromList [1..5] :: SerialT IO Int)+--+{-# INLINE foldlT #-}+foldlT :: (Monad m, IsStream t, Monad (s m), MonadTrans s)+ => (s m b -> a -> s m b) -> s m b -> t m a -> s m b+foldlT f z s = S.foldlT f z (toStreamS s)++{-# INLINE runFold #-}+runFold :: (Monad m, IsStream t) => Fold m a b -> t m a -> m b+runFold (Fold step begin done) = foldlMx' step begin done++------------------------------------------------------------------------------+-- Scans+------------------------------------------------------------------------------++-- postscanlM' followed by mapM+{-# INLINE postscanlMx' #-}+postscanlMx' :: (IsStream t, Monad m)+ => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> t m b+postscanlMx' step begin done m =+ D.fromStreamD $ D.postscanlMx' step begin done $ D.toStreamD m++-- postscanl' followed by map+{-# INLINE postscanlx' #-}+postscanlx' :: (IsStream t, Monad m)+ => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b+postscanlx' step begin done m =+ D.fromStreamD $ D.postscanlx' step begin done $ D.toStreamD m++-- scanlM' followed by mapM+--+{-# INLINE scanlMx' #-}+scanlMx' :: (IsStream t, Monad m)+ => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> t m b+scanlMx' step begin done m =+ D.fromStreamD $ D.scanlMx' step begin done $ D.toStreamD m++-- scanl followed by map+--+-- | Strict left scan with an extraction function. Like 'scanl'', but applies a+-- user supplied extraction function (the third argument) at each step. This is+-- designed to work with the @foldl@ library. The suffix @x@ is a mnemonic for+-- extraction.+--+-- @since 0.7.0+{-# INLINE scanlx' #-}+scanlx' :: (IsStream t, Monad m)+ => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b+scanlx' step begin done m =+ fromStreamS $ S.scanlx' step begin done $ toStreamS m++------------------------------------------------------------------------------+-- Comparison+------------------------------------------------------------------------------++-- | Compare two streams for equality+--+-- @since 0.5.3+{-# INLINE eqBy #-}+eqBy :: (IsStream t, Monad m) => (a -> b -> Bool) -> t m a -> t m b -> m Bool+eqBy f m1 m2 = D.eqBy f (D.toStreamD m1) (D.toStreamD m2)++-- | Compare two streams+--+-- @since 0.5.3+{-# INLINE cmpBy #-}+cmpBy+ :: (IsStream t, Monad m)+ => (a -> b -> Ordering) -> t m a -> t m b -> m Ordering+cmpBy f m1 m2 = D.cmpBy f (D.toStreamD m1) (D.toStreamD m2)++{-# INLINE minimum #-}+minimum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)+minimum m = S.minimum (toStreamS m)++{-# INLINE maximum #-}+maximum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)+maximum m = S.maximum (toStreamS m)++------------------------------------------------------------------------------+-- Fold Utilities+------------------------------------------------------------------------------++{-+-- XXX do we have facilities in Foldable to fold any Foldable in this manner?+--+-- | Perform a pair wise bottom up hierarchical fold of elements in the+-- container using the given function as the merge function.+--+-- This will perform a balanced merge sort if the merge function is+-- 'mergeBy compare'.+--+-- @since 0.7.0+{-# INLINABLE foldbWith #-}+foldbWith :: IsStream t+ => (t m a -> t m a -> t m a) -> SerialT Identity (t m a) -> t m a+foldbWith f = K.foldb f K.nil+-}++-- /Since: 0.7.0 ("Streamly.Prelude")/+--+-- | A variant of 'Data.Foldable.fold' that allows you to fold a 'Foldable'+-- container of streams using the specified stream sum operation.+--+-- @foldWith 'async' $ map return [1..3]@+--+-- Equivalent to:+--+-- @+-- foldWith f = S.foldMapWith f id+-- @+--+-- /Since: 0.1.0 ("Streamly")/+{-# INLINABLE foldWith #-}+foldWith :: (IsStream t, Foldable f)+ => (t m a -> t m a -> t m a) -> f (t m a) -> t m a+foldWith f = Prelude.foldr f K.nil++-- /Since: 0.7.0 ("Streamly.Prelude")/+--+-- | A variant of 'foldMap' that allows you to map a monadic streaming action+-- on a 'Foldable' container and then fold it using the specified stream merge+-- operation.+--+-- @foldMapWith 'async' return [1..3]@+--+-- Equivalent to:+--+-- @+-- foldMapWith f g xs = S.concatMapWith f g (S.fromFoldable xs)+-- @+--+-- /Since: 0.1.0 ("Streamly")/+{-# INLINABLE foldMapWith #-}+foldMapWith :: (IsStream t, Foldable f)+ => (t m b -> t m b -> t m b) -> (a -> t m b) -> f a -> t m b+foldMapWith f g = Prelude.foldr (f . g) K.nil++-- /Since: 0.7.0 ("Streamly.Prelude")/+--+-- | Like 'foldMapWith' but with the last two arguments reversed i.e. the+-- monadic streaming function is the last argument.+--+-- Equivalent to:+--+-- @+-- forEachWith = flip S.foldMapWith+-- @+--+-- /Since: 0.1.0 ("Streamly")/+{-# INLINABLE forEachWith #-}+forEachWith :: (IsStream t, Foldable f)+ => (t m b -> t m b -> t m b) -> f a -> (a -> t m b) -> t m b+forEachWith f xs g = Prelude.foldr (f . g) K.nil xs
+ src/Streamly/Internal/Data/Stream/SVar.hs view
@@ -0,0 +1,240 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}++#ifdef __HADDOCK_VERSION__+#undef INSPECTION+#endif++#ifdef INSPECTION+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}+#endif++-- |+-- Module : Streamly.Internal.Data.Stream.SVar+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.SVar+ ( fromSVar+ , fromStreamVar+ , fromProducer+ , fromConsumer+ , toSVar+ , pushToFold+ )+where++import Control.Exception (fromException)+import Control.Monad (when, void)+import Control.Monad.Catch (throwM)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)+import Data.Maybe (isNothing)+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)+import System.Mem (performMajorGC)++import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.StreamK hiding (reverse)++#ifdef INSPECTION+import Control.Exception (Exception)+import Control.Monad.Catch (MonadThrow)+import Control.Monad.Trans.Control (MonadBaseControl)+import Data.Typeable (Typeable)+import Test.Inspection (inspect, hasNoTypeClassesExcept)+#endif++-- | Pull a stream from an SVar.+{-# NOINLINE fromStreamVar #-}+fromStreamVar :: MonadAsync m => SVar Stream m a -> Stream m a+fromStreamVar sv = MkStream $ \st yld sng stp -> do+ list <- readOutputQ sv+ -- Reversing the output is important to guarantee that we process the+ -- outputs in the same order as they were generated by the constituent+ -- streams.+ foldStream st yld sng stp $ processEvents $ reverse list++ where++ allDone stp = do+ when (svarInspectMode sv) $ do+ t <- liftIO $ getTime Monotonic+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)+ liftIO $ printSVar sv "SVar Done"+ stp++ {-# INLINE processEvents #-}+ processEvents [] = MkStream $ \st yld sng stp -> do+ done <- postProcess sv+ if done+ then allDone stp+ else foldStream st yld sng stp $ fromStreamVar sv++ processEvents (ev : es) = MkStream $ \st yld sng stp -> do+ let rest = processEvents es+ case ev of+ ChildYield a -> yld a rest+ ChildStop tid e -> do+ accountThread sv tid+ case e of+ Nothing -> do+ stop <- shouldStop tid+ if stop+ then liftIO (cleanupSVar sv) >> allDone stp+ else foldStream st yld sng stp rest+ Just ex ->+ case fromException ex of+ Just ThreadAbort ->+ foldStream st yld sng stp rest+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex+ shouldStop tid =+ case svarStopStyle sv of+ StopNone -> return False+ StopAny -> return True+ StopBy -> do+ sid <- liftIO $ readIORef (svarStopBy sv)+ return $ if tid == sid then True else False++#ifdef INSPECTION+-- Use of GHC constraint tuple (GHC.Classes.(%,,%)) in fromStreamVar leads to+-- space leak because the tuple gets allocated in every recursive call and each+-- allocation holds on to the previous allocation. This test is to make sure+-- that we do not use the constraint tuple type class.+--+inspect $ hasNoTypeClassesExcept 'fromStreamVar+ [ ''Monad+ , ''Applicative+ , ''MonadThrow+ , ''Exception+ , ''MonadIO+ , ''MonadBaseControl+ , ''Typeable+ , ''Functor+ ]+#endif++{-# INLINE fromSVar #-}+fromSVar :: (MonadAsync m, IsStream t) => SVar Stream m a -> t m a+fromSVar sv =+ mkStream $ \st yld sng stp -> do+ ref <- liftIO $ newIORef ()+ _ <- liftIO $ mkWeakIORef ref hook+ -- We pass a copy of sv to fromStreamVar, so that we know that it has+ -- no other references, when that copy gets garbage collected "ref"+ -- will get garbage collected and our hook will be called.+ foldStreamShared st yld sng stp $+ fromStream $ fromStreamVar sv{svarRef = Just ref}+ where++ hook = do+ when (svarInspectMode sv) $ do+ r <- liftIO $ readIORef (svarStopTime (svarStats sv))+ when (isNothing r) $+ printSVar sv "SVar Garbage Collected"+ cleanupSVar sv+ -- If there are any SVars referenced by this SVar a GC will prompt+ -- them to be cleaned up quickly.+ when (svarInspectMode sv) performMajorGC++-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then+-- be read back from the SVar using 'fromSVar'.+toSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> m ()+toSVar sv m = toStreamVar sv (toStream m)++-------------------------------------------------------------------------------+-- Process events received by a fold consumer from a stream producer+-------------------------------------------------------------------------------++-- | Pull a stream from an SVar.+{-# NOINLINE fromProducer #-}+fromProducer :: MonadAsync m => SVar Stream m a -> Stream m a+fromProducer sv = mkStream $ \st yld sng stp -> do+ list <- readOutputQ sv+ -- Reversing the output is important to guarantee that we process the+ -- outputs in the same order as they were generated by the constituent+ -- streams.+ foldStream st yld sng stp $ processEvents $ reverse list++ where++ allDone stp = do+ when (svarInspectMode sv) $ do+ t <- liftIO $ getTime Monotonic+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)+ liftIO $ printSVar sv "SVar Done"+ sendStopToProducer sv+ stp++ {-# INLINE processEvents #-}+ processEvents [] = mkStream $ \st yld sng stp -> do+ foldStream st yld sng stp $ fromProducer sv++ processEvents (ev : es) = mkStream $ \_ yld _ stp -> do+ let rest = processEvents es+ case ev of+ ChildYield a -> yld a rest+ ChildStop tid e -> do+ accountThread sv tid+ case e of+ Nothing -> allDone stp+ Just _ -> error "Bug: fromProducer: received exception"++-------------------------------------------------------------------------------+-- Process events received by the producer thread from the consumer side+-------------------------------------------------------------------------------++-- XXX currently only one event is sent by a fold consumer to the stream+-- producer. But we can potentially have multiple events e.g. the fold step can+-- generate exception more than once and the producer can ignore those+-- exceptions or handle them and still keep driving the fold.+--+{-# NOINLINE fromConsumer #-}+fromConsumer :: MonadAsync m => SVar Stream m a -> m Bool+fromConsumer sv = do+ (list, _) <- liftIO $ readOutputQBasic (outputQueueFromConsumer sv)+ -- Reversing the output is important to guarantee that we process the+ -- outputs in the same order as they were generated by the constituent+ -- streams.+ processEvents $ reverse list++ where++ {-# INLINE processEvents #-}+ processEvents [] = return False+ processEvents (ev : _) = do+ case ev of+ ChildStop _ e -> do+ case e of+ Nothing -> return True+ Just ex -> throwM ex+ ChildYield _ -> error "Bug: fromConsumer: invalid ChildYield event"++-- push values to a fold worker via an SVar. Returns whether the fold is done.+{-# INLINE pushToFold #-}+pushToFold :: MonadAsync m => SVar Stream m a -> a -> m Bool+pushToFold sv a = do+ -- Check for exceptions before decrement so that we do not+ -- block forever if the child already exited with an exception.+ --+ -- We avoid a race between the consumer fold sending an event and we+ -- blocking on decrementBufferLimit by waking up the producer thread in+ -- sendToProducer before any event is sent by the fold to the producer+ -- stream.+ let qref = outputQueueFromConsumer sv+ done <- do+ (_, n) <- liftIO $ readIORef qref+ if (n > 0)+ then fromConsumer sv+ else return False+ if done+ then return True+ else liftIO $ do+ decrementBufferLimit sv+ void $ send sv (ChildYield a)+ return False
+ src/Streamly/Internal/Data/Stream/Serial.hs view
@@ -0,0 +1,472 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving#-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++-- |+-- Module : Streamly.Internal.Data.Stream.Serial+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Serial+ (+ -- * Serial appending stream+ SerialT+ , Serial+ , K.serial+ , serially++ -- * Serial interleaving stream+ , WSerialT+ , WSerial+ , wSerial+ , wSerialFst+ , wSerialMin+ , wSerially++ -- * Construction+ , unfoldrM++ -- * Transformation+ , map+ , mapM++ -- * Deprecated+ , StreamT+ , InterleavedT+ , (<=>)+ , interleaving+ )+where++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData(..))+#if MIN_VERSION_deepseq(1,4,3)+import Control.DeepSeq (NFData1(..))+#endif+import Control.Monad.Base (MonadBase(..), liftBaseDefault)+import Control.Monad.Catch (MonadThrow, throwM)+-- import Control.Monad.Error.Class (MonadError(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader.Class (MonadReader(..))+import Control.Monad.State.Class (MonadState(..))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Data.Foldable (Foldable(foldl'), fold)+import Data.Functor.Identity (Identity(..), runIdentity)+import Data.Maybe (fromMaybe)+import Data.Semigroup (Endo(..))+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif+import GHC.Exts (IsList(..), IsString(..))+import Text.Read (Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec,+ readListPrecDefault)+import Prelude hiding (map, mapM, errorWithoutStackTrace)++import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace)+import Streamly.Internal.Data.Stream.StreamK (IsStream(..), adapt, Stream, mkStream,+ foldStream)+import Streamly.Internal.Data.Strict (Maybe'(..), toMaybe)+import qualified Streamly.Internal.Data.Stream.Prelude as P+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++#include "Instances.hs"+#include "inline.hs"++------------------------------------------------------------------------------+-- SerialT+------------------------------------------------------------------------------++-- | The 'Semigroup' operation for 'SerialT' behaves like a regular append+-- operation. Therefore, when @a <> b@ is evaluated, stream @a@ is evaluated+-- first until it exhausts and then stream @b@ is evaluated. In other words,+-- the elements of stream @b@ are appended to the elements of stream @a@. This+-- operation can be used to fold an infinite lazy container of streams.+--+-- @+-- import Streamly+-- import qualified "Streamly.Prelude" as S+--+-- main = (S.toList . 'serially' $ (S.fromList [1,2]) \<\> (S.fromList [3,4])) >>= print+-- @+-- @+-- [1,2,3,4]+-- @+--+-- The 'Monad' instance runs the /monadic continuation/ for each+-- element of the stream, serially.+--+-- @+-- main = S.drain . 'serially' $ do+-- x <- return 1 \<\> return 2+-- S.yieldM $ print x+-- @+-- @+-- 1+-- 2+-- @+--+-- 'SerialT' nests streams serially in a depth first manner.+--+-- @+-- main = S.drain . 'serially' $ do+-- x <- return 1 \<\> return 2+-- y <- return 3 \<\> return 4+-- S.yieldM $ print (x, y)+-- @+-- @+-- (1,3)+-- (1,4)+-- (2,3)+-- (2,4)+-- @+--+-- We call the monadic code being run for each element of the stream a monadic+-- continuation. In imperative paradigm we can think of this composition as+-- nested @for@ loops and the monadic continuation is the body of the loop. The+-- loop iterates for all elements of the stream.+--+-- Note that the behavior and semantics of 'SerialT', including 'Semigroup'+-- and 'Monad' instances are exactly like Haskell lists except that 'SerialT'+-- can contain effectful actions while lists are pure.+--+-- In the code above, the 'serially' combinator can be omitted as the default+-- stream type is 'SerialT'.+--+-- @since 0.2.0+newtype SerialT m a = SerialT {getSerialT :: Stream m a}+ deriving (Semigroup, Monoid, MonadTrans)++-- | A serial IO stream of elements of type @a@. See 'SerialT' documentation+-- for more details.+--+-- @since 0.2.0+type Serial = SerialT IO++-- |+-- @since 0.1.0+{-# DEPRECATED StreamT "Please use 'SerialT' instead." #-}+type StreamT = SerialT++-- | Fix the type of a polymorphic stream as 'SerialT'.+--+-- @since 0.1.0+serially :: IsStream t => SerialT m a -> t m a+serially = adapt++{-# INLINE consMSerial #-}+{-# SPECIALIZE consMSerial :: IO a -> SerialT IO a -> SerialT IO a #-}+consMSerial :: Monad m => m a -> SerialT m a -> SerialT m a+consMSerial m ms = fromStream $ K.consMStream m (toStream ms)++instance IsStream SerialT where+ toStream = getSerialT+ fromStream = SerialT+ consM = consMSerial+ (|:) = consMSerial++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++instance Monad m => Monad (SerialT m) where+ return = pure+ {-# INLINE (>>=) #-}+ (>>=) = K.bindWith K.serial+ {-# INLINE (>>) #-}+ (>>) = (*>)++ -- StreamD based implementation+ -- return = SerialT . D.fromStreamD . D.yield+ -- m >>= f = D.fromStreamD $ D.concatMap (\a -> D.toStreamD (f a)) (D.toStreamD m)++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++{-# INLINE mapM #-}+mapM :: (IsStream t, Monad m) => (a -> m b) -> t m a -> t m b+mapM f m = D.fromStreamD $ D.mapM f $ D.toStreamD m++-- |+-- @+-- map = fmap+-- @+--+-- Same as 'fmap'.+--+-- @+-- > S.toList $ S.map (+1) $ S.fromList [1,2,3]+-- [2,3,4]+-- @+--+-- @since 0.4.0+{-# INLINE map #-}+map :: (IsStream t, Monad m) => (a -> b) -> t m a -> t m b+map f = mapM (return . f)++{-# INLINE apSerial #-}+apSerial :: Monad m => SerialT m (a -> b) -> SerialT m a -> SerialT m b+apSerial (SerialT m1) (SerialT m2) = D.fromStreamD $ D.toStreamD m1 <*> D.toStreamD m2++{-# INLINE apSequence #-}+apSequence :: Monad m => SerialT m a -> SerialT m b -> SerialT m b+apSequence (SerialT m1) (SerialT m2) = D.fromStreamD $ D.toStreamD m1 *> D.toStreamD m2++instance Monad m => Applicative (SerialT m) where+ {-# INLINE pure #-}+ pure = SerialT . K.yield+ {-# INLINE (<*>) #-}+ (<*>) = apSerial+ {-# INLINE (*>) #-}+ (*>) = apSequence++MONAD_COMMON_INSTANCES(SerialT,)+LIST_INSTANCES(SerialT)+NFDATA1_INSTANCE(SerialT)+FOLDABLE_INSTANCE(SerialT)+TRAVERSABLE_INSTANCE(SerialT)++------------------------------------------------------------------------------+-- WSerialT+------------------------------------------------------------------------------++-- | The 'Semigroup' operation for 'WSerialT' interleaves the elements from the+-- two streams. Therefore, when @a <> b@ is evaluated, stream @a@ is evaluated+-- first to produce the first element of the combined stream and then stream+-- @b@ is evaluated to produce the next element of the combined stream, and+-- then we go back to evaluating stream @a@ and so on. In other words, the+-- elements of stream @a@ are interleaved with the elements of stream @b@.+--+-- Note that evaluation of @a <> b <> c@ does not schedule @a@, @b@ and @c@+-- with equal priority. This expression is equivalent to @a <> (b <> c)@,+-- therefore, it fairly interleaves @a@ with the result of @b <> c@. For+-- example, @S.fromList [1,2] <> S.fromList [3,4] <> S.fromList [5,6] ::+-- WSerialT Identity Int@ would result in [1,3,2,5,4,6]. In other words, the+-- leftmost stream gets the same scheduling priority as the rest of the+-- streams taken together. The same is true for each subexpression on the right.+--+-- Note that this operation cannot be used to fold a container of infinite+-- streams as the state that it needs to maintain is proportional to the number+-- of streams.+--+-- The @W@ in the name stands for @wide@ or breadth wise scheduling in+-- contrast to the depth wise scheduling behavior of 'SerialT'.+--+-- @+-- import Streamly+-- import qualified "Streamly.Prelude" as S+--+-- main = (S.toList . 'wSerially' $ (S.fromList [1,2]) \<\> (S.fromList [3,4])) >>= print+-- @+-- @+-- [1,3,2,4]+-- @+--+-- Similarly, the 'Monad' instance interleaves the iterations of the+-- inner and the outer loop, nesting loops in a breadth first manner.+--+--+-- @+-- main = S.drain . 'wSerially' $ do+-- x <- return 1 \<\> return 2+-- y <- return 3 \<\> return 4+-- S.yieldM $ print (x, y)+-- @+-- @+-- (1,3)+-- (2,3)+-- (1,4)+-- (2,4)+-- @+--+-- @since 0.2.0+newtype WSerialT m a = WSerialT {getWSerialT :: Stream m a}+ deriving (MonadTrans)++-- | An interleaving serial IO stream of elements of type @a@. See 'WSerialT'+-- documentation for more details.+--+-- @since 0.2.0+type WSerial = WSerialT IO++-- |+-- @since 0.1.0+{-# DEPRECATED InterleavedT "Please use 'WSerialT' instead." #-}+type InterleavedT = WSerialT++-- | Fix the type of a polymorphic stream as 'WSerialT'.+--+-- @since 0.2.0+wSerially :: IsStream t => WSerialT m a -> t m a+wSerially = adapt++-- | Same as 'wSerially'.+--+-- @since 0.1.0+{-# DEPRECATED interleaving "Please use wSerially instead." #-}+interleaving :: IsStream t => WSerialT m a -> t m a+interleaving = wSerially++consMWSerial :: Monad m => m a -> WSerialT m a -> WSerialT m a+consMWSerial m ms = fromStream $ K.consMStream m (toStream ms)++instance IsStream WSerialT where+ toStream = getWSerialT+ fromStream = WSerialT++ {-# INLINE consM #-}+ {-# SPECIALIZE consM :: IO a -> WSerialT IO a -> WSerialT IO a #-}+ consM :: Monad m => m a -> WSerialT m a -> WSerialT m a+ consM = consMWSerial++ {-# INLINE (|:) #-}+ {-# SPECIALIZE (|:) :: IO a -> WSerialT IO a -> WSerialT IO a #-}+ (|:) :: Monad m => m a -> WSerialT m a -> WSerialT m a+ (|:) = consMWSerial++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++-- Additionally we can have m elements yield from the first stream and n+-- elements yielding from the second stream. We can also have time slicing+-- variants of positional interleaving, e.g. run first stream for m seconds and+-- run the second stream for n seconds.+--+-- Similar combinators can be implemented using WAhead style.++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WSerialT'.+-- Interleaves two streams, yielding one element from each stream alternately.+-- When one stream stops the rest of the other stream is used in the output+-- stream.+--+-- @since 0.2.0+{-# INLINE wSerial #-}+wSerial :: IsStream t => t m a -> t m a -> t m a+wSerial m1 m2 = mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp m2+ single a = yld a m2+ yieldk a r = yld a (wSerial m2 r)+ foldStream st yieldk single stop m1++-- | Like `wSerial` but stops interleaving as soon as the first stream stops.+--+-- @since 0.7.0+{-# INLINE wSerialFst #-}+wSerialFst :: IsStream t => t m a -> t m a -> t m a+wSerialFst m1 m2 = mkStream $ \st yld sng stp -> do+ let yieldFirst a r = yld a (yieldSecond r m2)+ in foldStream st yieldFirst sng stp m1++ where++ yieldSecond s1 s2 = mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp s1+ single a = yld a s1+ yieldk a r = yld a (wSerial s1 r)+ in foldStream st yieldk single stop s2++-- | Like `wSerial` but stops interleaving as soon as any of the two streams+-- stops.+--+-- @since 0.7.0+{-# INLINE wSerialMin #-}+wSerialMin :: IsStream t => t m a -> t m a -> t m a+wSerialMin m1 m2 = mkStream $ \st yld sng stp -> do+ let stop = stp+ single a = sng a+ yieldk a r = yld a (wSerial m2 r)+ foldStream st yieldk single stop m1++instance Semigroup (WSerialT m a) where+ (<>) = wSerial++infixr 5 <=>++-- | Same as 'wSerial'.+--+-- @since 0.1.0+{-# DEPRECATED (<=>) "Please use 'wSerial' instead." #-}+{-# INLINE (<=>) #-}+(<=>) :: IsStream t => t m a -> t m a -> t m a+(<=>) = wSerial++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance Monoid (WSerialT m a) where+ mempty = K.nil+ mappend = (<>)++{-# INLINE apWSerial #-}+apWSerial :: Monad m => WSerialT m (a -> b) -> WSerialT m a -> WSerialT m b+apWSerial (WSerialT m1) (WSerialT m2) =+ let f x1 = K.concatMapBy wSerial (pure . x1) m2+ in WSerialT $ K.concatMapBy wSerial f m1++instance Monad m => Applicative (WSerialT m) where+ {-# INLINE pure #-}+ pure = WSerialT . K.yield+ {-# INLINE (<*>) #-}+ (<*>) = apWSerial++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++instance Monad m => Monad (WSerialT m) where+ return = pure+ {-# INLINE (>>=) #-}+ (>>=) = K.bindWith wSerial++------------------------------------------------------------------------------+-- Other instances+------------------------------------------------------------------------------++MONAD_COMMON_INSTANCES(WSerialT,)+LIST_INSTANCES(WSerialT)+NFDATA1_INSTANCE(WSerialT)+FOLDABLE_INSTANCE(WSerialT)+TRAVERSABLE_INSTANCE(WSerialT)++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++-- | Build a stream by unfolding a /monadic/ step function starting from a+-- seed. The step function returns the next element in the stream and the next+-- seed value. When it is done it returns 'Nothing' and the stream ends. For+-- example,+--+-- @+-- let f b =+-- if b > 3+-- then return Nothing+-- else print b >> return (Just (b, b + 1))+-- in drain $ unfoldrM f 0+-- @+-- @+-- 0+-- 1+-- 2+-- 3+-- @+--+-- /Internal/+--+{-# INLINE unfoldrM #-}+unfoldrM :: (IsStream t, Monad m) => (b -> m (Maybe (a, b))) -> b -> t m a+unfoldrM step seed = D.fromStreamD (D.unfoldrM step seed)
+ src/Streamly/Internal/Data/Stream/StreamD.hs view
@@ -0,0 +1,4271 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MagicHash #-}++#if __GLASGOW_HASKELL__ >= 801+{-# LANGUAGE TypeApplications #-}+#endif++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.StreamD+-- Copyright : (c) 2018 Harendra Kumar+-- (c) Roman Leshchinskiy 2008-2010+-- (c) The University of Glasgow, 2009+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Direct style re-implementation of CPS style stream in StreamK module. The+-- symbol or suffix 'D' in this module denotes the "Direct" style. GHC is able+-- to INLINE and fuse direct style better, providing better performance than+-- CPS implementation.+--+-- @+-- import qualified Streamly.Internal.Data.Stream.StreamD as D+-- @++-- Some of the functions in this file have been adapted from the vector+-- library, https://hackage.haskell.org/package/vector.++module Streamly.Internal.Data.Stream.StreamD+ (+ -- * The stream type+ Step (..)++#if __GLASGOW_HASKELL__ >= 800+ , Stream (Stream, UnStream)+#else+ , Stream (UnStream)+ , pattern Stream+#endif++ -- * Construction+ , nil+ , nilM+ , cons++ -- * Deconstruction+ , uncons++ -- * Generation+ -- ** Unfolds+ , unfoldr+ , unfoldrM+ , unfold++ -- ** Specialized Generation+ -- | Generate a monadic stream from a seed.+ , repeat+ , repeatM+ , replicate+ , replicateM+ , fromIndices+ , fromIndicesM+ , generate+ , generateM+ , iterate+ , iterateM++ -- ** Enumerations+ , enumerateFromStepIntegral+ , enumerateFromIntegral+ , enumerateFromThenIntegral+ , enumerateFromToIntegral+ , enumerateFromThenToIntegral++ , enumerateFromStepNum+ , numFrom+ , numFromThen+ , enumerateFromToFractional+ , enumerateFromThenToFractional++ -- ** Time+ , currentTime++ -- ** Conversions+ -- | Transform an input structure into a stream.+ -- | Direct style stream does not support @fromFoldable@.+ , yield+ , yieldM+ , fromList+ , fromListM+ , fromStreamK+ , fromStreamD+ , fromPrimVar+ , fromSVar++ -- * Elimination+ -- ** General Folds+ , foldrS+ , foldrT+ , foldrM+ , foldrMx+ , foldr+ , foldr1++ , foldl'+ , foldlM'+ , foldlS+ , foldlT+ , reverse+ , reverse'++ , foldlx'+ , foldlMx'+ , runFold++ -- ** Specialized Folds+ , tap+ , tapOffsetEvery+ , tapAsync+ , tapRate+ , pollCounts+ , drain+ , null+ , head+ , headElse+ , tail+ , last+ , elem+ , notElem+ , all+ , any+ , maximum+ , maximumBy+ , minimum+ , minimumBy+ , findIndices+ , lookup+ , findM+ , find+ , (!!)+ , toSVarParallel++ -- ** Flattening nested streams+ , concatMapM+ , concatMap+ , ConcatMapUState (..)+ , concatMapU+ , ConcatUnfoldInterleaveState (..)+ , concatUnfoldInterleave+ , concatUnfoldRoundrobin+ , AppendState(..)+ , append+ , InterleaveState(..)+ , interleave+ , interleaveMin+ , interleaveSuffix+ , interleaveInfix+ , roundRobin -- interleaveFair?/ParallelFair+ , gintercalateSuffix+ , interposeSuffix+ , gintercalate+ , interpose++ -- ** Grouping+ , groupsOf+ , groupsOf2+ , groupsBy+ , groupsRollingBy++ -- ** Splitting+ , splitBy+ , splitSuffixBy+ , wordsBy+ , splitSuffixBy'++ , splitOn+ , splitSuffixOn++ , splitInnerBy+ , splitInnerBySuffix++ -- ** Substreams+ , isPrefixOf+ , isSubsequenceOf+ , stripPrefix++ -- ** Map and Fold+ , mapM_++ -- ** Conversions+ -- | Transform a stream into another type.+ , toList+ , toListRev+ , toStreamK+ , toStreamD++ , hoist+ , generally++ , liftInner+ , runReaderT+ , evalStateT+ , runStateT++ -- * Transformation+ , transform++ -- ** By folding (scans)+ , scanlM'+ , scanl'+ , scanlM+ , scanl+ , scanl1M'+ , scanl1'+ , scanl1M+ , scanl1++ , prescanl'+ , prescanlM'++ , postscanl+ , postscanlM+ , postscanl'+ , postscanlM'++ , postscanlx'+ , postscanlMx'+ , scanlMx'+ , scanlx'++ -- * Filtering+ , filter+ , filterM+ , uniq+ , take+ , takeByTime+ , takeWhile+ , takeWhileM+ , drop+ , dropByTime+ , dropWhile+ , dropWhileM++ -- * Mapping+ , map+ , mapM+ , sequence+ , rollingMap+ , rollingMapM++ -- * Inserting+ , intersperseM+ , intersperse+ , intersperseSuffix+ , intersperseSuffixBySpan+ , insertBy++ -- * Deleting+ , deleteBy++ -- ** Map and Filter+ , mapMaybe+ , mapMaybeM++ -- * Zipping+ , indexed+ , indexedR+ , zipWith+ , zipWithM++ -- * Comparisons+ , eqBy+ , cmpBy++ -- * Merging+ , mergeBy+ , mergeByM++ -- * Transformation comprehensions+ , the++ -- * Exceptions+ , newFinalizedIORef+ , runIORefFinalizer+ , clearIORefFinalizer+ , gbracket+ , before+ , after+ , afterIO+ , bracket+ , bracketIO+ , onException+ , finally+ , finallyIO+ , handle++ -- * Concurrent Application+ , mkParallel+ , mkParallelD++ , lastN+ )+where++import Control.Concurrent (killThread, myThreadId, takeMVar, threadDelay)+import Control.Exception+ (Exception, SomeException, AsyncException, fromException)+import Control.Monad (void, when, forever)+import Control.Monad.Catch (MonadCatch, throwM)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader (ReaderT)+import Control.Monad.State.Strict (StateT)+import Control.Monad.Trans (MonadTrans(lift))+import Control.Monad.Trans.Control (MonadBaseControl)+import Data.Bits (shiftR, shiftL, (.|.), (.&.))+import Data.Functor.Identity (Identity(..))+import Data.Int (Int64)+import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)+import Data.Maybe (fromJust, isJust, isNothing)+import Data.Word (Word32)+import Foreign.Ptr (Ptr)+import Foreign.Storable (Storable(..))+import GHC.Types (SPEC(..))+import System.Mem (performMajorGC)+import Prelude+ hiding (map, mapM, mapM_, repeat, foldr, last, take, filter,+ takeWhile, drop, dropWhile, all, any, maximum, minimum, elem,+ notElem, null, head, tail, zipWith, lookup, foldr1, sequence,+ (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,+ reverse, iterate)++import qualified Control.Monad.Catch as MC+import qualified Control.Monad.Reader as Reader+import qualified Control.Monad.State.Strict as State+import qualified Prelude++import Streamly.Internal.Mutable.Prim.Var+ (Prim, Var, readVar, newVar, modifyVar')+import Streamly.Internal.Data.Time.Units+ (TimeUnit64, toRelTime64, diffAbsTime64)++import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)+import Streamly.Internal.Memory.Array.Types (Array(..))+import Streamly.Internal.Data.Fold.Types (Fold(..))+import Streamly.Internal.Data.Pipe.Types (Pipe(..), PipeState(..))+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)+import Streamly.Internal.Data.Time.Units+ (MicroSecond64(..), fromAbsTime, toAbsTime, AbsTime)+import Streamly.Internal.Data.Unfold.Types (Unfold(..))+import Streamly.Internal.Data.Strict (Tuple3'(..))++import Streamly.Internal.Data.Stream.StreamD.Type+import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.SVar (fromConsumer, pushToFold)++import qualified Streamly.Internal.Data.Pipe.Types as Pipe+import qualified Streamly.Internal.Memory.Array.Types as A+import qualified Streamly.Internal.Data.Fold as FL+import qualified Streamly.Memory.Ring as RB+import qualified Streamly.Internal.Data.Stream.StreamK as K++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++-- | An empty 'Stream'.+{-# INLINE_NORMAL nil #-}+nil :: Monad m => Stream m a+nil = Stream (\_ _ -> return Stop) ()++-- | An empty 'Stream' with a side effect.+{-# INLINE_NORMAL nilM #-}+nilM :: Monad m => m b -> Stream m a+nilM m = Stream (\_ _ -> m >> return Stop) ()++{-# INLINE_NORMAL consM #-}+consM :: Monad m => m a -> Stream m a -> Stream m a+consM m (Stream step state) = Stream step1 Nothing+ where+ {-# INLINE_LATE step1 #-}+ step1 _ Nothing = m >>= \x -> return $ Yield x (Just state)+ step1 gst (Just st) = do+ r <- step gst st+ return $+ case r of+ Yield a s -> Yield a (Just s)+ Skip s -> Skip (Just s)+ Stop -> Stop++-- XXX implement in terms of consM?+-- cons x = consM (return x)+--+-- | Can fuse but has O(n^2) complexity.+{-# INLINE_NORMAL cons #-}+cons :: Monad m => a -> Stream m a -> Stream m a+cons x (Stream step state) = Stream step1 Nothing+ where+ {-# INLINE_LATE step1 #-}+ step1 _ Nothing = return $ Yield x (Just state)+ step1 gst (Just st) = do+ r <- step gst st+ return $+ case r of+ Yield a s -> Yield a (Just s)+ Skip s -> Skip (Just s)+ Stop -> Stop++-------------------------------------------------------------------------------+-- Deconstruction+-------------------------------------------------------------------------------++-- Does not fuse, has the same performance as the StreamK version.+{-# INLINE_NORMAL uncons #-}+uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))+uncons (UnStream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield x s -> return $ Just (x, Stream step s)+ Skip s -> go s+ Stop -> return Nothing++------------------------------------------------------------------------------+-- Generation by unfold+------------------------------------------------------------------------------++{-# INLINE_NORMAL unfoldrM #-}+unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a+unfoldrM next state = Stream step state+ where+ {-# INLINE_LATE step #-}+ step _ st = do+ r <- next st+ return $ case r of+ Just (x, s) -> Yield x s+ Nothing -> Stop++{-# INLINE_LATE unfoldr #-}+unfoldr :: Monad m => (s -> Maybe (a, s)) -> s -> Stream m a+unfoldr f = unfoldrM (return . f)++-- | Convert an 'Unfold' into a 'Stream' by supplying it a seed.+--+{-# INLINE_NORMAL unfold #-}+unfold :: Monad m => Unfold m a b -> a -> Stream m b+unfold (Unfold ustep inject) seed = Stream step Nothing+ where+ {-# INLINE_LATE step #-}+ step _ Nothing = inject seed >>= return . Skip . Just+ step _ (Just st) = do+ r <- ustep st+ return $ case r of+ Yield x s -> Yield x (Just s)+ Skip s -> Skip (Just s)+ Stop -> Stop++------------------------------------------------------------------------------+-- Specialized Generation+------------------------------------------------------------------------------++{-# INLINE_NORMAL repeatM #-}+repeatM :: Monad m => m a -> Stream m a+repeatM x = Stream (\_ _ -> x >>= \r -> return $ Yield r ()) ()++{-# INLINE_NORMAL repeat #-}+repeat :: Monad m => a -> Stream m a+repeat x = Stream (\_ _ -> return $ Yield x ()) ()++{-# INLINE_NORMAL iterateM #-}+iterateM :: Monad m => (a -> m a) -> m a -> Stream m a+iterateM step = Stream (\_ st -> st >>= \x -> return $ Yield x (step x))++{-# INLINE_NORMAL iterate #-}+iterate :: Monad m => (a -> a) -> a -> Stream m a+iterate step st = iterateM (return . step) (return st)++{-# INLINE_NORMAL replicateM #-}+replicateM :: forall m a. Monad m => Int -> m a -> Stream m a+replicateM n p = Stream step n+ where+ {-# INLINE_LATE step #-}+ step _ (i :: Int)+ | i <= 0 = return Stop+ | otherwise = do+ x <- p+ return $ Yield x (i - 1)++{-# INLINE_NORMAL replicate #-}+replicate :: Monad m => Int -> a -> Stream m a+replicate n x = replicateM n (return x)++-- This would not work properly for floats, therefore we put an Integral+-- constraint.+-- | Can be used to enumerate unbounded integrals. This does not check for+-- overflow or underflow for bounded integrals.+{-# INLINE_NORMAL enumerateFromStepIntegral #-}+enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a+enumerateFromStepIntegral from stride =+ from `seq` stride `seq` Stream step from+ where+ {-# INLINE_LATE step #-}+ step _ !x = return $ Yield x $! (x + stride)++-- We are assuming that "to" is constrained by the type to be within+-- max/min bounds.+{-# INLINE enumerateFromToIntegral #-}+enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a+enumerateFromToIntegral from to =+ takeWhile (<= to) $ enumerateFromStepIntegral from 1++{-# INLINE enumerateFromIntegral #-}+enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a+enumerateFromIntegral from = enumerateFromToIntegral from maxBound++data EnumState a = EnumInit | EnumYield a a a | EnumStop++{-# INLINE_NORMAL enumerateFromThenToIntegralUp #-}+enumerateFromThenToIntegralUp+ :: (Monad m, Integral a)+ => a -> a -> a -> Stream m a+enumerateFromThenToIntegralUp from next to = Stream step EnumInit+ where+ {-# INLINE_LATE step #-}+ step _ EnumInit =+ return $+ if to < next+ then if to < from+ then Stop+ else Yield from EnumStop+ else -- from <= next <= to+ let stride = next - from+ in Skip $ EnumYield from stride (to - stride)++ step _ (EnumYield x stride toMinus) =+ return $+ if x > toMinus+ then Yield x EnumStop+ else Yield x $ EnumYield (x + stride) stride toMinus++ step _ EnumStop = return Stop++{-# INLINE_NORMAL enumerateFromThenToIntegralDn #-}+enumerateFromThenToIntegralDn+ :: (Monad m, Integral a)+ => a -> a -> a -> Stream m a+enumerateFromThenToIntegralDn from next to = Stream step EnumInit+ where+ {-# INLINE_LATE step #-}+ step _ EnumInit =+ return $ if to > next+ then if to > from+ then Stop+ else Yield from EnumStop+ else -- from >= next >= to+ let stride = next - from+ in Skip $ EnumYield from stride (to - stride)++ step _ (EnumYield x stride toMinus) =+ return $+ if x < toMinus+ then Yield x EnumStop+ else Yield x $ EnumYield (x + stride) stride toMinus++ step _ EnumStop = return Stop++{-# INLINE_NORMAL enumerateFromThenToIntegral #-}+enumerateFromThenToIntegral+ :: (Monad m, Integral a)+ => a -> a -> a -> Stream m a+enumerateFromThenToIntegral from next to+ | next >= from = enumerateFromThenToIntegralUp from next to+ | otherwise = enumerateFromThenToIntegralDn from next to++{-# INLINE_NORMAL enumerateFromThenIntegral #-}+enumerateFromThenIntegral+ :: (Monad m, Integral a, Bounded a)+ => a -> a -> Stream m a+enumerateFromThenIntegral from next =+ if next > from+ then enumerateFromThenToIntegralUp from next maxBound+ else enumerateFromThenToIntegralDn from next minBound++-- For floating point numbers if the increment is less than the precision then+-- it just gets lost. Therefore we cannot always increment it correctly by just+-- repeated addition.+-- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15+-- 9007199254740992 + 2 :: Double => 9.007199254740994e15++-- Instead we accumulate the increment counter and compute the increment+-- every time before adding it to the starting number.+--+-- This works for Integrals as well as floating point numbers, but+-- enumerateFromStepIntegral is faster for integrals.+{-# INLINE_NORMAL enumerateFromStepNum #-}+enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a+enumerateFromStepNum from stride = Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1)++{-# INLINE_NORMAL numFrom #-}+numFrom :: (Monad m, Num a) => a -> Stream m a+numFrom from = enumerateFromStepNum from 1++{-# INLINE_NORMAL numFromThen #-}+numFromThen :: (Monad m, Num a) => a -> a -> Stream m a+numFromThen from next = enumerateFromStepNum from (next - from)++-- We cannot write a general function for Num. The only way to write code+-- portable between the two is to use a 'Real' constraint and convert between+-- Fractional and Integral using fromRational which is horribly slow.+{-# INLINE_NORMAL enumerateFromToFractional #-}+enumerateFromToFractional+ :: (Monad m, Fractional a, Ord a)+ => a -> a -> Stream m a+enumerateFromToFractional from to =+ takeWhile (<= to + 1 / 2) $ enumerateFromStepNum from 1++{-# INLINE_NORMAL enumerateFromThenToFractional #-}+enumerateFromThenToFractional+ :: (Monad m, Fractional a, Ord a)+ => a -> a -> a -> Stream m a+enumerateFromThenToFractional from next to =+ takeWhile predicate $ numFromThen from next+ where+ mid = (next - from) / 2+ predicate | next >= from = (<= to + mid)+ | otherwise = (>= to + mid)++-------------------------------------------------------------------------------+-- Generation by Conversion+-------------------------------------------------------------------------------++{-# INLINE_NORMAL fromIndicesM #-}+fromIndicesM :: Monad m => (Int -> m a) -> Stream m a+fromIndicesM gen = Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i = do+ x <- gen i+ return $ Yield x (i + 1)++{-# INLINE fromIndices #-}+fromIndices :: Monad m => (Int -> a) -> Stream m a+fromIndices gen = fromIndicesM (return . gen)++{-# INLINE_NORMAL generateM #-}+generateM :: Monad m => Int -> (Int -> m a) -> Stream m a+generateM n gen = n `seq` Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i | i < n = do+ x <- gen i+ return $ Yield x (i + 1)+ | otherwise = return Stop++{-# INLINE generate #-}+generate :: Monad m => Int -> (Int -> a) -> Stream m a+generate n gen = generateM n (return . gen)++-- XXX we need the MonadAsync constraint because of a rewrite rule.+-- | Convert a list of monadic actions to a 'Stream'+{-# INLINE_LATE fromListM #-}+fromListM :: MonadAsync m => [m a] -> Stream m a+fromListM = Stream step+ where+ {-# INLINE_LATE step #-}+ step _ (m:ms) = m >>= \x -> return $ Yield x ms+ step _ [] = return Stop++{-# INLINE toStreamD #-}+toStreamD :: (K.IsStream t, Monad m) => t m a -> Stream m a+toStreamD = fromStreamK . K.toStream++{-# INLINE_NORMAL fromPrimVar #-}+fromPrimVar :: (MonadIO m, Prim a) => Var IO a -> Stream m a+fromPrimVar var = Stream step ()+ where+ {-# INLINE_LATE step #-}+ step _ () = liftIO (readVar var) >>= \x -> return $ Yield x ()++-------------------------------------------------------------------------------+-- Generation from SVar+-------------------------------------------------------------------------------++data FromSVarState t m a =+ FromSVarInit+ | FromSVarRead (SVar t m a)+ | FromSVarLoop (SVar t m a) [ChildEvent a]+ | FromSVarDone (SVar t m a)++{-# INLINE_NORMAL fromSVar #-}+fromSVar :: (MonadAsync m) => SVar t m a -> Stream m a+fromSVar svar = Stream step FromSVarInit+ where++ {-# INLINE_LATE step #-}+ step _ FromSVarInit = do+ ref <- liftIO $ newIORef ()+ _ <- liftIO $ mkWeakIORef ref hook+ -- when this copy of svar gets garbage collected "ref" will get+ -- garbage collected and our GC hook will be called.+ let sv = svar{svarRef = Just ref}+ return $ Skip (FromSVarRead sv)++ where++ {-# NOINLINE hook #-}+ hook = do+ when (svarInspectMode svar) $ do+ r <- liftIO $ readIORef (svarStopTime (svarStats svar))+ when (isNothing r) $+ printSVar svar "SVar Garbage Collected"+ cleanupSVar svar+ -- If there are any SVars referenced by this SVar a GC will prompt+ -- them to be cleaned up quickly.+ when (svarInspectMode svar) performMajorGC++ step _ (FromSVarRead sv) = do+ list <- readOutputQ sv+ -- Reversing the output is important to guarantee that we process the+ -- outputs in the same order as they were generated by the constituent+ -- streams.+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)++ step _ (FromSVarLoop sv []) = do+ done <- postProcess sv+ return $ Skip $ if done+ then (FromSVarDone sv)+ else (FromSVarRead sv)++ step _ (FromSVarLoop sv (ev : es)) = do+ case ev of+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)+ ChildStop tid e -> do+ accountThread sv tid+ case e of+ Nothing -> do+ stop <- shouldStop tid+ if stop+ then do+ liftIO (cleanupSVar sv)+ return $ Skip (FromSVarDone sv)+ else return $ Skip (FromSVarLoop sv es)+ Just ex ->+ case fromException ex of+ Just ThreadAbort ->+ return $ Skip (FromSVarLoop sv es)+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex+ where++ shouldStop tid =+ case svarStopStyle sv of+ StopNone -> return False+ StopAny -> return True+ StopBy -> do+ sid <- liftIO $ readIORef (svarStopBy sv)+ return $ if tid == sid then True else False++ step _ (FromSVarDone sv) = do+ when (svarInspectMode sv) $ do+ t <- liftIO $ getTime Monotonic+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)+ liftIO $ printSVar sv "SVar Done"+ return Stop++-------------------------------------------------------------------------------+-- Process events received by a fold consumer from a stream producer+-------------------------------------------------------------------------------++{-# INLINE_NORMAL fromProducer #-}+fromProducer :: (MonadAsync m) => SVar t m a -> Stream m a+fromProducer svar = Stream step (FromSVarRead svar)+ where++ {-# INLINE_LATE step #-}+ step _ (FromSVarRead sv) = do+ list <- readOutputQ sv+ -- Reversing the output is important to guarantee that we process the+ -- outputs in the same order as they were generated by the constituent+ -- streams.+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)++ step _ (FromSVarLoop sv []) = return $ Skip $ FromSVarRead sv+ step _ (FromSVarLoop sv (ev : es)) = do+ case ev of+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)+ ChildStop tid e -> do+ accountThread sv tid+ case e of+ Nothing -> do+ sendStopToProducer sv+ return $ Skip (FromSVarDone sv)+ Just _ -> error "Bug: fromProducer: received exception"++ step _ (FromSVarDone sv) = do+ when (svarInspectMode sv) $ do+ t <- liftIO $ getTime Monotonic+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)+ liftIO $ printSVar sv "SVar Done"+ return Stop++ step _ FromSVarInit = undefined++-------------------------------------------------------------------------------+-- Hoisting the inner monad+-------------------------------------------------------------------------------++{-# INLINE_NORMAL hoist #-}+hoist :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a+hoist f (Stream step state) = (Stream step' state)+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- f $ step (adaptState gst) st+ return $ case r of+ Yield x s -> Yield x s+ Skip s -> Skip s+ Stop -> Stop++{-# INLINE generally #-}+generally :: Monad m => Stream Identity a -> Stream m a+generally = hoist (return . runIdentity)++{-# INLINE_NORMAL liftInner #-}+liftInner :: (Monad m, MonadTrans t, Monad (t m))+ => Stream m a -> Stream (t m) a+liftInner (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- lift $ step (adaptState gst) st+ return $ case r of+ Yield x s -> Yield x s+ Skip s -> Skip s+ Stop -> Stop++{-# INLINE_NORMAL runReaderT #-}+runReaderT :: Monad m => s -> Stream (ReaderT s m) a -> Stream m a+runReaderT sval (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- Reader.runReaderT (step (adaptState gst) st) sval+ return $ case r of+ Yield x s -> Yield x s+ Skip s -> Skip s+ Stop -> Stop++{-# INLINE_NORMAL evalStateT #-}+evalStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m a+evalStateT sval (Stream step state) = Stream step' (state, sval)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, sv) = do+ (r, sv') <- State.runStateT (step (adaptState gst) st) sv+ return $ case r of+ Yield x s -> Yield x (s, sv')+ Skip s -> Skip (s, sv')+ Stop -> Stop++{-# INLINE_NORMAL runStateT #-}+runStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m (s, a)+runStateT sval (Stream step state) = Stream step' (state, sval)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, sv) = do+ (r, sv') <- State.runStateT (step (adaptState gst) st) sv+ return $ case r of+ Yield x s -> Yield (sv', x) (s, sv')+ Skip s -> Skip (s, sv')+ Stop -> Stop++------------------------------------------------------------------------------+-- Elimination by Folds+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Right Folds+------------------------------------------------------------------------------++{-# INLINE_NORMAL foldr1 #-}+foldr1 :: Monad m => (a -> a -> a) -> Stream m a -> m (Maybe a)+foldr1 f m = do+ r <- uncons m+ case r of+ Nothing -> return Nothing+ Just (h, t) -> fmap Just (foldr f h t)++------------------------------------------------------------------------------+-- Left Folds+------------------------------------------------------------------------------++{-# INLINE_NORMAL foldlT #-}+foldlT :: (Monad m, Monad (s m), MonadTrans s)+ => (s m b -> a -> s m b) -> s m b -> Stream m a -> s m b+foldlT fstep begin (Stream step state) = go SPEC begin state+ where+ go !_ acc st = do+ r <- lift $ step defState st+ case r of+ Yield x s -> go SPEC (fstep acc x) s+ Skip s -> go SPEC acc s+ Stop -> acc++-- Note, this is going to have horrible performance, because of the nature of+-- the stream type (i.e. direct stream vs CPS). Its only for reference, it is+-- likely be practically unusable.+{-# INLINE_NORMAL foldlS #-}+foldlS :: Monad m+ => (Stream m b -> a -> Stream m b) -> Stream m b -> Stream m a -> Stream m b+foldlS fstep begin (Stream step state) = Stream step' (Left (state, begin))+ where+ step' gst (Left (st, acc)) = do+ r <- step (adaptState gst) st+ return $ case r of+ Yield x s -> Skip (Left (s, fstep acc x))+ Skip s -> Skip (Left (s, acc))+ Stop -> Skip (Right acc)++ step' gst (Right (Stream stp stt)) = do+ r <- stp (adaptState gst) stt+ return $ case r of+ Yield x s -> Yield x (Right (Stream stp s))+ Skip s -> Skip (Right (Stream stp s))+ Stop -> Stop++------------------------------------------------------------------------------+-- Specialized Folds+------------------------------------------------------------------------------++-- | Run a streaming composition, discard the results.+{-# INLINE_LATE drain #-}+drain :: Monad m => Stream m a -> m ()+-- drain = foldrM (\_ xs -> xs) (return ())+drain (Stream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield _ s -> go SPEC s+ Skip s -> go SPEC s+ Stop -> return ()++{-# INLINE_NORMAL null #-}+null :: Monad m => Stream m a -> m Bool+null m = foldrM (\_ _ -> return False) (return True) m++{-# INLINE_NORMAL head #-}+head :: Monad m => Stream m a -> m (Maybe a)+head m = foldrM (\x _ -> return (Just x)) (return Nothing) m++{-# INLINE_NORMAL headElse #-}+headElse :: Monad m => a -> Stream m a -> m a+headElse a m = foldrM (\x _ -> return x) (return a) m++-- Does not fuse, has the same performance as the StreamK version.+{-# INLINE_NORMAL tail #-}+tail :: Monad m => Stream m a -> m (Maybe (Stream m a))+tail (UnStream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield _ s -> return (Just $ Stream step s)+ Skip s -> go s+ Stop -> return Nothing++-- XXX will it fuse? need custom impl?+{-# INLINE_NORMAL last #-}+last :: Monad m => Stream m a -> m (Maybe a)+last = foldl' (\_ y -> Just y) Nothing++{-# INLINE_NORMAL elem #-}+elem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+-- elem e m = foldrM (\x xs -> if x == e then return True else xs) (return False) m+elem e (Stream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield x s+ | x == e -> return True+ | otherwise -> go s+ Skip s -> go s+ Stop -> return False++{-# INLINE_NORMAL notElem #-}+notElem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+notElem e s = fmap not (elem e s)++{-# INLINE_NORMAL all #-}+all :: Monad m => (a -> Bool) -> Stream m a -> m Bool+-- all p m = foldrM (\x xs -> if p x then xs else return False) (return True) m+all p (Stream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield x s+ | p x -> go s+ | otherwise -> return False+ Skip s -> go s+ Stop -> return True++{-# INLINE_NORMAL any #-}+any :: Monad m => (a -> Bool) -> Stream m a -> m Bool+-- any p m = foldrM (\x xs -> if p x then return True else xs) (return False) m+any p (Stream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield x s+ | p x -> return True+ | otherwise -> go s+ Skip s -> go s+ Stop -> return False++{-# INLINE_NORMAL maximum #-}+maximum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+maximum (Stream step state) = go Nothing state+ where+ go Nothing st = do+ r <- step defState st+ case r of+ Yield x s -> go (Just x) s+ Skip s -> go Nothing s+ Stop -> return Nothing+ go (Just acc) st = do+ r <- step defState st+ case r of+ Yield x s+ | acc <= x -> go (Just x) s+ | otherwise -> go (Just acc) s+ Skip s -> go (Just acc) s+ Stop -> return (Just acc)++{-# INLINE_NORMAL maximumBy #-}+maximumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+maximumBy cmp (Stream step state) = go Nothing state+ where+ go Nothing st = do+ r <- step defState st+ case r of+ Yield x s -> go (Just x) s+ Skip s -> go Nothing s+ Stop -> return Nothing+ go (Just acc) st = do+ r <- step defState st+ case r of+ Yield x s -> case cmp acc x of+ GT -> go (Just acc) s+ _ -> go (Just x) s+ Skip s -> go (Just acc) s+ Stop -> return (Just acc)++{-# INLINE_NORMAL minimum #-}+minimum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+minimum (Stream step state) = go Nothing state+ where+ go Nothing st = do+ r <- step defState st+ case r of+ Yield x s -> go (Just x) s+ Skip s -> go Nothing s+ Stop -> return Nothing+ go (Just acc) st = do+ r <- step defState st+ case r of+ Yield x s+ | acc <= x -> go (Just acc) s+ | otherwise -> go (Just x) s+ Skip s -> go (Just acc) s+ Stop -> return (Just acc)++{-# INLINE_NORMAL minimumBy #-}+minimumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+minimumBy cmp (Stream step state) = go Nothing state+ where+ go Nothing st = do+ r <- step defState st+ case r of+ Yield x s -> go (Just x) s+ Skip s -> go Nothing s+ Stop -> return Nothing+ go (Just acc) st = do+ r <- step defState st+ case r of+ Yield x s -> case cmp acc x of+ GT -> go (Just x) s+ _ -> go (Just acc) s+ Skip s -> go (Just acc) s+ Stop -> return (Just acc)++{-# INLINE_NORMAL (!!) #-}+(!!) :: (Monad m) => Stream m a -> Int -> m (Maybe a)+(Stream step state) !! i = go i state+ where+ go n st = do+ r <- step defState st+ case r of+ Yield x s | n < 0 -> return Nothing+ | n == 0 -> return $ Just x+ | otherwise -> go (n - 1) s+ Skip s -> go n s+ Stop -> return Nothing++{-# INLINE_NORMAL lookup #-}+lookup :: (Monad m, Eq a) => a -> Stream m (a, b) -> m (Maybe b)+lookup e m = foldrM (\(a, b) xs -> if e == a then return (Just b) else xs)+ (return Nothing) m++{-# INLINE_NORMAL findM #-}+findM :: Monad m => (a -> m Bool) -> Stream m a -> m (Maybe a)+findM p m = foldrM (\x xs -> p x >>= \r -> if r then return (Just x) else xs)+ (return Nothing) m++{-# INLINE find #-}+find :: Monad m => (a -> Bool) -> Stream m a -> m (Maybe a)+find p = findM (return . p)++{-# INLINE_NORMAL findIndices #-}+findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int+findIndices p (Stream step state) = Stream step' (state, 0)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, i) = i `seq` do+ r <- step (adaptState gst) st+ return $ case r of+ Yield x s -> if p x then Yield i (s, i+1) else Skip (s, i+1)+ Skip s -> Skip (s, i)+ Stop -> Stop++{-# INLINE toListRev #-}+toListRev :: Monad m => Stream m a -> m [a]+toListRev = foldl' (flip (:)) []++-- We can implement reverse as:+--+-- > reverse = foldlS (flip cons) nil+--+-- However, this implementation is unusable because of the horrible performance+-- of cons. So we just convert it to a list first and then stream from the+-- list.+--+-- XXX Maybe we can use an Array instead of a list here?+{-# INLINE_NORMAL reverse #-}+reverse :: Monad m => Stream m a -> Stream m a+reverse m = Stream step Nothing+ where+ {-# INLINE_LATE step #-}+ step _ Nothing = do+ xs <- toListRev m+ return $ Skip (Just xs)+ step _ (Just (x:xs)) = return $ Yield x (Just xs)+ step _ (Just []) = return Stop++-- Much faster reverse for Storables+{-# INLINE_NORMAL reverse' #-}+reverse' :: forall m a. (MonadIO m, Storable a) => Stream m a -> Stream m a+{-+-- This commented implementation copies the whole stream into one single array+-- and then streams from that array, this is 3-4x faster than the chunked code+-- that follows. Though this could be problematic due to unbounded large+-- allocations. We need to figure out why the chunked code is slower and if we+-- can optimize the chunked code to work as fast as this one. It may be a+-- fusion issue?+import Foreign.ForeignPtr (touchForeignPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)+import Foreign.Ptr (Ptr, plusPtr)+reverse' m = Stream step Nothing+ where+ {-# INLINE_LATE step #-}+ step _ Nothing = do+ arr <- A.fromStreamD m+ let p = aEnd arr `plusPtr` negate (sizeOf (undefined :: a))+ return $ Skip $ Just (aStart arr, p)++ step _ (Just (start, p)) | p < unsafeForeignPtrToPtr start = return Stop++ step _ (Just (start, p)) = do+ let !x = A.unsafeInlineIO $ do+ r <- peek p+ touchForeignPtr start+ return r+ next = p `plusPtr` negate (sizeOf (undefined :: a))+ return $ Yield x (Just (start, next))+-}+reverse' m =+ A.flattenArraysRev+ $ fromStreamK+ $ K.reverse+ $ toStreamK+ $ A.fromStreamDArraysOf A.defaultChunkSize m+++------------------------------------------------------------------------------+-- Grouping/Splitting+------------------------------------------------------------------------------++{-# INLINE_NORMAL splitSuffixBy' #-}+splitSuffixBy' :: Monad m+ => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitSuffixBy' predicate f (Stream step state) =+ Stream (stepOuter f) (Just state)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter (Fold fstep initial done) gst (Just st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- fstep acc x+ if (predicate x)+ then done acc' >>= \val -> return $ Yield val (Just s)+ else go SPEC s acc'++ Skip s -> return $ Skip $ Just s+ Stop -> return Stop++ where++ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ acc' <- fstep acc x+ if (predicate x)+ then done acc' >>= \val -> return $ Yield val (Just s)+ else go SPEC s acc'+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \val -> return $ Yield val Nothing++ stepOuter _ _ Nothing = return Stop++{-# INLINE_NORMAL groupsBy #-}+groupsBy :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Stream m a+ -> Stream m b+groupsBy cmp f (Stream step state) = Stream (stepOuter f) (Just state, Nothing)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter (Fold fstep initial done) gst (Just st, Nothing) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- fstep acc x+ go SPEC x s acc'++ Skip s -> return $ Skip $ (Just s, Nothing)+ Stop -> return Stop++ where++ go !_ prev stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if cmp x prev+ then do+ acc' <- fstep acc x+ go SPEC prev s acc'+ else done acc >>= \r -> return $ Yield r (Just s, Just x)+ Skip s -> go SPEC prev s acc+ Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)++ stepOuter (Fold fstep initial done) gst (Just st, Just prev) = do+ acc <- initial+ acc' <- fstep acc prev+ go SPEC st acc'++ where++ -- XXX code duplicated from the previous equation+ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if cmp x prev+ then do+ acc' <- fstep acc x+ go SPEC s acc'+ else done acc >>= \r -> return $ Yield r (Just s, Just x)+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)++ stepOuter _ _ (Nothing,_) = return Stop++{-# INLINE_NORMAL groupsRollingBy #-}+groupsRollingBy :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Stream m a+ -> Stream m b+groupsRollingBy cmp f (Stream step state) =+ Stream (stepOuter f) (Just state, Nothing)+ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter (Fold fstep initial done) gst (Just st, Nothing) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- fstep acc x+ go SPEC x s acc'++ Skip s -> return $ Skip $ (Just s, Nothing)+ Stop -> return Stop++ where+ go !_ prev stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if cmp prev x+ then do+ acc' <- fstep acc x+ go SPEC x s acc'+ else+ done acc >>= \r -> return $ Yield r (Just s, Just x)+ Skip s -> go SPEC prev s acc+ Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)++ stepOuter (Fold fstep initial done) gst (Just st, Just prev') = do+ acc <- initial+ acc' <- fstep acc prev'+ go SPEC prev' st acc'++ where+ go !_ prevv stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if cmp prevv x+ then do+ acc' <- fstep acc x+ go SPEC x s acc'+ else done acc >>= \r -> return $ Yield r (Just s, Just x)+ Skip s -> go SPEC prevv s acc+ Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)++ stepOuter _ _ (Nothing, _) = return Stop++{-# INLINE_NORMAL splitBy #-}+splitBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitBy predicate f (Stream step state) = Stream (step' f) (Just state)++ where++ {-# INLINE_LATE step' #-}+ step' (Fold fstep initial done) gst (Just st) = initial >>= go SPEC st++ where++ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if predicate x+ then done acc >>= \r -> return $ Yield r (Just s)+ else do+ acc' <- fstep acc x+ go SPEC s acc'+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r Nothing++ step' _ _ Nothing = return Stop++-- XXX requires -funfolding-use-threshold=150 in lines-unlines benchmark+{-# INLINE_NORMAL splitSuffixBy #-}+splitSuffixBy :: Monad m+ => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitSuffixBy predicate f (Stream step state) = Stream (step' f) (Just state)++ where++ {-# INLINE_LATE step' #-}+ step' (Fold fstep initial done) gst (Just st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ if predicate x+ then done acc >>= \val -> return $ Yield val (Just s)+ else do+ acc' <- fstep acc x+ go SPEC s acc'++ Skip s -> return $ Skip $ Just s+ Stop -> return Stop++ where++ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if predicate x+ then done acc >>= \r -> return $ Yield r (Just s)+ else do+ acc' <- fstep acc x+ go SPEC s acc'+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r Nothing++ step' _ _ Nothing = return Stop++{-# INLINE_NORMAL wordsBy #-}+wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+wordsBy predicate f (Stream step state) = Stream (stepOuter f) (Just state)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter (Fold fstep initial done) gst (Just st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ if predicate x+ then return $ Skip (Just s)+ else do+ acc <- initial+ acc' <- fstep acc x+ go SPEC s acc'++ Skip s -> return $ Skip $ Just s+ Stop -> return Stop++ where++ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if predicate x+ then done acc >>= \r -> return $ Yield r (Just s)+ else do+ acc' <- fstep acc x+ go SPEC s acc'+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r Nothing++ stepOuter _ _ Nothing = return Stop++-- String search algorithms:+-- http://www-igm.univ-mlv.fr/~lecroq/string/index.html++{-+-- TODO can we unify the splitting operations using a splitting configuration+-- like in the split package.+--+data SplitStyle = Infix | Suffix | Prefix deriving (Eq, Show)++data SplitOptions = SplitOptions+ { style :: SplitStyle+ , withSep :: Bool -- ^ keep the separators in output+ -- , compact :: Bool -- ^ treat multiple consecutive separators as one+ -- , trimHead :: Bool -- ^ drop blank at head+ -- , trimTail :: Bool -- ^ drop blank at tail+ }+-}++data SplitOnState s a =+ GO_START+ | GO_EMPTY_PAT s+ | GO_SINGLE_PAT s a+ | GO_SHORT_PAT s+ | GO_KARP_RABIN s !(RB.Ring a) !(Ptr a)+ | GO_DONE++{-# INLINE_NORMAL splitOn #-}+splitOn+ :: forall m a b. (MonadIO m, Storable a, Enum a, Eq a)+ => Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitOn patArr@Array{..} (Fold fstep initial done) (Stream step state) =+ Stream stepOuter GO_START++ where++ patLen = A.length patArr+ maxIndex = patLen - 1+ elemBits = sizeOf (undefined :: a) * 8++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ GO_START =+ if patLen == 0+ then return $ Skip $ GO_EMPTY_PAT state+ else if patLen == 1+ then do+ r <- liftIO $ (A.unsafeIndexIO patArr 0)+ return $ Skip $ GO_SINGLE_PAT state r+ else if sizeOf (undefined :: a) * patLen+ <= sizeOf (undefined :: Word)+ then return $ Skip $ GO_SHORT_PAT state+ else do+ (rb, rhead) <- liftIO $ RB.new patLen+ return $ Skip $ GO_KARP_RABIN state rb rhead++ stepOuter gst (GO_SINGLE_PAT stt pat) = initial >>= go SPEC stt++ where++ go !_ st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ if pat == x+ then do+ r <- done acc+ return $ Yield r (GO_SINGLE_PAT s pat)+ else fstep acc x >>= go SPEC s+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r GO_DONE++ stepOuter gst (GO_SHORT_PAT stt) = initial >>= go0 SPEC 0 (0 :: Word) stt++ where++ mask :: Word+ mask = (1 `shiftL` (elemBits * patLen)) - 1++ addToWord wrd a = (wrd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ patWord :: Word+ patWord = mask .&. A.foldl' addToWord 0 patArr++ go0 !_ !idx wrd st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd' = addToWord wrd x+ if idx == maxIndex+ then do+ if wrd' .&. mask == patWord+ then do+ r <- done acc+ return $ Yield r (GO_SHORT_PAT s)+ else go1 SPEC wrd' s acc+ else go0 SPEC (idx + 1) wrd' s acc+ Skip s -> go0 SPEC idx wrd s acc+ Stop -> do+ acc' <- if idx /= 0+ then go2 wrd idx acc+ else return acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ {-# INLINE go1 #-}+ go1 !_ wrd st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd' = addToWord wrd x+ old = (mask .&. wrd) `shiftR` (elemBits * (patLen - 1))+ acc' <- fstep acc (toEnum $ fromIntegral old)+ if wrd' .&. mask == patWord+ then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)+ else go1 SPEC wrd' s acc'+ Skip s -> go1 SPEC wrd s acc+ Stop -> do+ acc' <- go2 wrd patLen acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ go2 !wrd !n !acc | n > 0 = do+ let old = (mask .&. wrd) `shiftR` (elemBits * (n - 1))+ fstep acc (toEnum $ fromIntegral old) >>= go2 wrd (n - 1)+ go2 _ _ acc = return acc++ stepOuter gst (GO_KARP_RABIN stt rb rhead) = do+ initial >>= go0 SPEC 0 rhead stt++ where++ k = 2891336453 :: Word32+ coeff = k ^ patLen+ addCksum cksum a = cksum * k + fromIntegral (fromEnum a)+ deltaCksum cksum old new =+ addCksum cksum new - coeff * fromIntegral (fromEnum old)++ -- XXX shall we use a random starting hash or 1 instead of 0?+ patHash = A.foldl' addCksum 0 patArr++ -- rh == ringHead+ go0 !_ !idx !rh st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ rh' <- liftIO $ RB.unsafeInsert rb rh x+ if idx == maxIndex+ then do+ let fold = RB.unsafeFoldRing (RB.ringBound rb)+ let !ringHash = fold addCksum 0 rb+ if ringHash == patHash+ then go2 SPEC ringHash rh' s acc+ else go1 SPEC ringHash rh' s acc+ else go0 SPEC (idx + 1) rh' s acc+ Skip s -> go0 SPEC idx rh s acc+ Stop -> do+ !acc' <- if idx /= 0+ then RB.unsafeFoldRingM rh fstep acc rb+ else return acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ -- XXX Theoretically this code can do 4 times faster if GHC generates+ -- optimal code. If we use just "(cksum' == patHash)" condition it goes+ -- 4x faster, as soon as we add the "RB.unsafeEqArray rb v" condition+ -- the generated code changes drastically and becomes 4x slower. Need+ -- to investigate what is going on with GHC.+ {-# INLINE go1 #-}+ go1 !_ !cksum !rh st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ old <- liftIO $ peek rh+ let cksum' = deltaCksum cksum old x+ acc' <- fstep acc old++ if (cksum' == patHash)+ then do+ rh' <- liftIO (RB.unsafeInsert rb rh x)+ go2 SPEC cksum' rh' s acc'+ else do+ rh' <- liftIO (RB.unsafeInsert rb rh x)+ go1 SPEC cksum' rh' s acc'+ Skip s -> go1 SPEC cksum rh s acc+ Stop -> do+ acc' <- RB.unsafeFoldRingFullM rh fstep acc rb+ done acc' >>= \r -> return $ Yield r GO_DONE++ go2 !_ !cksum' !rh' s !acc' = do+ if RB.unsafeEqArray rb rh' patArr+ then do+ r <- done acc'+ return $ Yield r (GO_KARP_RABIN s rb rhead)+ else go1 SPEC cksum' rh' s acc'++ stepOuter gst (GO_EMPTY_PAT st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- fstep acc x+ done acc' >>= \r -> return $ Yield r (GO_EMPTY_PAT s)+ Skip s -> return $ Skip (GO_EMPTY_PAT s)+ Stop -> return Stop++ stepOuter _ GO_DONE = return Stop++{-# INLINE_NORMAL splitSuffixOn #-}+splitSuffixOn+ :: forall m a b. (MonadIO m, Storable a, Enum a, Eq a)+ => Bool+ -> Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitSuffixOn withSep patArr@Array{..} (Fold fstep initial done)+ (Stream step state) =+ Stream stepOuter GO_START++ where++ patLen = A.length patArr+ maxIndex = patLen - 1+ elemBits = sizeOf (undefined :: a) * 8++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ GO_START =+ if patLen == 0+ then return $ Skip $ GO_EMPTY_PAT state+ else if patLen == 1+ then do+ r <- liftIO $ (A.unsafeIndexIO patArr 0)+ return $ Skip $ GO_SINGLE_PAT state r+ else if sizeOf (undefined :: a) * patLen+ <= sizeOf (undefined :: Word)+ then return $ Skip $ GO_SHORT_PAT state+ else do+ (rb, rhead) <- liftIO $ RB.new patLen+ return $ Skip $ GO_KARP_RABIN state rb rhead++ stepOuter gst (GO_SINGLE_PAT stt pat) = do+ -- This first part is the only difference between splitOn and+ -- splitSuffixOn.+ -- If the last element is a separator do not issue a blank segment.+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ acc <- initial+ if pat == x+ then do+ acc' <- if withSep then fstep acc x else return acc+ done acc' >>= \r -> return $ Yield r (GO_SINGLE_PAT s pat)+ else fstep acc x >>= go SPEC s+ Skip s -> return $ Skip $ (GO_SINGLE_PAT s pat)+ Stop -> return Stop++ where++ -- This is identical for splitOn and splitSuffixOn+ go !_ st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ if pat == x+ then do+ acc' <- if withSep then fstep acc x else return acc+ r <- done acc'+ return $ Yield r (GO_SINGLE_PAT s pat)+ else fstep acc x >>= go SPEC s+ Skip s -> go SPEC s acc+ Stop -> done acc >>= \r -> return $ Yield r GO_DONE++ stepOuter gst (GO_SHORT_PAT stt) = do++ -- Call "initial" only if the stream yields an element, otherwise we+ -- may call "initial" but never yield anything. initial may produce a+ -- side effect, therefore we will end up doing and discard a side+ -- effect.++ let idx = 0+ let wrd = 0+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ acc <- initial+ let wrd' = addToWord wrd x+ acc' <- if withSep then fstep acc x else return acc+ if idx == maxIndex+ then do+ if wrd' .&. mask == patWord+ then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)+ else go0 SPEC (idx + 1) wrd' s acc'+ else go0 SPEC (idx + 1) wrd' s acc'+ Skip s -> return $ Skip (GO_SHORT_PAT s)+ Stop -> return Stop++ where++ mask :: Word+ mask = (1 `shiftL` (elemBits * patLen)) - 1++ addToWord wrd a = (wrd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ patWord :: Word+ patWord = mask .&. A.foldl' addToWord 0 patArr++ go0 !_ !idx wrd st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd' = addToWord wrd x+ acc' <- if withSep then fstep acc x else return acc+ if idx == maxIndex+ then do+ if wrd' .&. mask == patWord+ then do+ r <- done acc'+ return $ Yield r (GO_SHORT_PAT s)+ else go1 SPEC wrd' s acc'+ else go0 SPEC (idx + 1) wrd' s acc'+ Skip s -> go0 SPEC idx wrd s acc+ Stop -> do+ if (idx == maxIndex) && (wrd .&. mask == patWord)+ then return Stop+ else do+ acc' <- if idx /= 0 && not withSep+ then go2 wrd idx acc+ else return acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ {-# INLINE go1 #-}+ go1 !_ wrd st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd' = addToWord wrd x+ old = (mask .&. wrd) `shiftR` (elemBits * (patLen - 1))+ acc' <- if withSep+ then fstep acc x+ else fstep acc (toEnum $ fromIntegral old)+ if wrd' .&. mask == patWord+ then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)+ else go1 SPEC wrd' s acc'+ Skip s -> go1 SPEC wrd s acc+ Stop ->+ -- If the last sequence is a separator do not issue a blank+ -- segment.+ if wrd .&. mask == patWord+ then return Stop+ else do+ acc' <- if withSep+ then return acc+ else go2 wrd patLen acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ go2 !wrd !n !acc | n > 0 = do+ let old = (mask .&. wrd) `shiftR` (elemBits * (n - 1))+ fstep acc (toEnum $ fromIntegral old) >>= go2 wrd (n - 1)+ go2 _ _ acc = return acc++ stepOuter gst (GO_KARP_RABIN stt rb rhead) = do+ let idx = 0+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- if withSep then fstep acc x else return acc+ rh' <- liftIO (RB.unsafeInsert rb rhead x)+ if idx == maxIndex+ then do+ let fold = RB.unsafeFoldRing (RB.ringBound rb)+ let !ringHash = fold addCksum 0 rb+ if ringHash == patHash+ then go2 SPEC ringHash rh' s acc'+ else go0 SPEC (idx + 1) rh' s acc'+ else go0 SPEC (idx + 1) rh' s acc'+ Skip s -> return $ Skip (GO_KARP_RABIN s rb rhead)+ Stop -> return Stop++ where++ k = 2891336453 :: Word32+ coeff = k ^ patLen+ addCksum cksum a = cksum * k + fromIntegral (fromEnum a)+ deltaCksum cksum old new =+ addCksum cksum new - coeff * fromIntegral (fromEnum old)++ -- XXX shall we use a random starting hash or 1 instead of 0?+ patHash = A.foldl' addCksum 0 patArr++ -- rh == ringHead+ go0 !_ !idx !rh st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc' <- if withSep then fstep acc x else return acc+ rh' <- liftIO (RB.unsafeInsert rb rh x)+ if idx == maxIndex+ then do+ let fold = RB.unsafeFoldRing (RB.ringBound rb)+ let !ringHash = fold addCksum 0 rb+ if ringHash == patHash+ then go2 SPEC ringHash rh' s acc'+ else go1 SPEC ringHash rh' s acc'+ else go0 SPEC (idx + 1) rh' s acc'+ Skip s -> go0 SPEC idx rh s acc+ Stop -> do+ -- do not issue a blank segment when we end at pattern+ if (idx == maxIndex) && RB.unsafeEqArray rb rh patArr+ then return Stop+ else do+ !acc' <- if idx /= 0 && not withSep+ then RB.unsafeFoldRingM rh fstep acc rb+ else return acc+ done acc' >>= \r -> return $ Yield r GO_DONE++ -- XXX Theoretically this code can do 4 times faster if GHC generates+ -- optimal code. If we use just "(cksum' == patHash)" condition it goes+ -- 4x faster, as soon as we add the "RB.unsafeEqArray rb v" condition+ -- the generated code changes drastically and becomes 4x slower. Need+ -- to investigate what is going on with GHC.+ {-# INLINE go1 #-}+ go1 !_ !cksum !rh st !acc = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ old <- liftIO $ peek rh+ let cksum' = deltaCksum cksum old x+ acc' <- if withSep+ then fstep acc x+ else fstep acc old++ if (cksum' == patHash)+ then do+ rh' <- liftIO (RB.unsafeInsert rb rh x)+ go2 SPEC cksum' rh' s acc'+ else do+ rh' <- liftIO (RB.unsafeInsert rb rh x)+ go1 SPEC cksum' rh' s acc'+ Skip s -> go1 SPEC cksum rh s acc+ Stop -> do+ if RB.unsafeEqArray rb rh patArr+ then return Stop+ else do+ acc' <- if withSep+ then return acc+ else RB.unsafeFoldRingFullM rh fstep acc rb+ done acc' >>= \r -> return $ Yield r GO_DONE++ go2 !_ !cksum' !rh' s !acc' = do+ if RB.unsafeEqArray rb rh' patArr+ then do+ r <- done acc'+ return $ Yield r (GO_KARP_RABIN s rb rhead)+ else go1 SPEC cksum' rh' s acc'++ stepOuter gst (GO_EMPTY_PAT st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ acc <- initial+ acc' <- fstep acc x+ done acc' >>= \r -> return $ Yield r (GO_EMPTY_PAT s)+ Skip s -> return $ Skip (GO_EMPTY_PAT s)+ Stop -> return Stop++ stepOuter _ GO_DONE = return Stop++data SplitState s arr+ = SplitInitial s+ | SplitBuffering s arr+ | SplitSplitting s arr+ | SplitYielding arr (SplitState s arr)+ | SplitFinishing++-- XXX An alternative approach would be to use a partial fold (Fold m a b) to+-- split using a splitBy like combinator. The Fold would consume upto the+-- separator and return any leftover which can then be fed to the next fold.+--+-- We can revisit this once we have partial folds/parsers.+--+-- | Performs infix separator style splitting.+{-# INLINE_NORMAL splitInnerBy #-}+splitInnerBy+ :: Monad m+ => (f a -> m (f a, Maybe (f a))) -- splitter+ -> (f a -> f a -> m (f a)) -- joiner+ -> Stream m (f a)+ -> Stream m (f a)+splitInnerBy splitter joiner (Stream step1 state1) =+ (Stream step (SplitInitial state1))++ where++ {-# INLINE_LATE step #-}+ step gst (SplitInitial st) = do+ r <- step1 gst st+ case r of+ Yield x s -> do+ (x1, mx2) <- splitter x+ return $ case mx2 of+ Nothing -> Skip (SplitBuffering s x1)+ Just x2 -> Skip (SplitYielding x1 (SplitSplitting s x2))+ Skip s -> return $ Skip (SplitInitial s)+ Stop -> return $ Stop++ step gst (SplitBuffering st buf) = do+ r <- step1 gst st+ case r of+ Yield x s -> do+ (x1, mx2) <- splitter x+ buf' <- joiner buf x1+ return $ case mx2 of+ Nothing -> Skip (SplitBuffering s buf')+ Just x2 -> Skip (SplitYielding buf' (SplitSplitting s x2))+ Skip s -> return $ Skip (SplitBuffering s buf)+ Stop -> return $ Skip (SplitYielding buf SplitFinishing)++ step _ (SplitSplitting st buf) = do+ (x1, mx2) <- splitter buf+ return $ case mx2 of+ Nothing -> Skip $ SplitBuffering st x1+ Just x2 -> Skip $ SplitYielding x1 (SplitSplitting st x2)++ step _ (SplitYielding x next) = return $ Yield x next+ step _ SplitFinishing = return $ Stop++-- | Performs infix separator style splitting.+{-# INLINE_NORMAL splitInnerBySuffix #-}+splitInnerBySuffix+ :: (Monad m, Eq (f a), Monoid (f a))+ => (f a -> m (f a, Maybe (f a))) -- splitter+ -> (f a -> f a -> m (f a)) -- joiner+ -> Stream m (f a)+ -> Stream m (f a)+splitInnerBySuffix splitter joiner (Stream step1 state1) =+ (Stream step (SplitInitial state1))++ where++ {-# INLINE_LATE step #-}+ step gst (SplitInitial st) = do+ r <- step1 gst st+ case r of+ Yield x s -> do+ (x1, mx2) <- splitter x+ return $ case mx2 of+ Nothing -> Skip (SplitBuffering s x1)+ Just x2 -> Skip (SplitYielding x1 (SplitSplitting s x2))+ Skip s -> return $ Skip (SplitInitial s)+ Stop -> return $ Stop++ step gst (SplitBuffering st buf) = do+ r <- step1 gst st+ case r of+ Yield x s -> do+ (x1, mx2) <- splitter x+ buf' <- joiner buf x1+ return $ case mx2 of+ Nothing -> Skip (SplitBuffering s buf')+ Just x2 -> Skip (SplitYielding buf' (SplitSplitting s x2))+ Skip s -> return $ Skip (SplitBuffering s buf)+ Stop -> return $+ if buf == mempty+ then Stop+ else Skip (SplitYielding buf SplitFinishing)++ step _ (SplitSplitting st buf) = do+ (x1, mx2) <- splitter buf+ return $ case mx2 of+ Nothing -> Skip $ SplitBuffering st x1+ Just x2 -> Skip $ SplitYielding x1 (SplitSplitting st x2)++ step _ (SplitYielding x next) = return $ Yield x next+ step _ SplitFinishing = return $ Stop++------------------------------------------------------------------------------+-- Substreams+------------------------------------------------------------------------------++{-# INLINE_NORMAL isPrefixOf #-}+isPrefixOf :: (Eq a, Monad m) => Stream m a -> Stream m a -> m Bool+isPrefixOf (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)+ where+ go (sa, sb, Nothing) = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go (sa', sb, Just x)+ Skip sa' -> go (sa', sb, Nothing)+ Stop -> return True++ go (sa, sb, Just x) = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go (sa, sb', Nothing)+ else return False+ Skip sb' -> go (sa, sb', Just x)+ Stop -> return False++{-# INLINE_NORMAL isSubsequenceOf #-}+isSubsequenceOf :: (Eq a, Monad m) => Stream m a -> Stream m a -> m Bool+isSubsequenceOf (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)+ where+ go (sa, sb, Nothing) = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go (sa', sb, Just x)+ Skip sa' -> go (sa', sb, Nothing)+ Stop -> return True++ go (sa, sb, Just x) = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go (sa, sb', Nothing)+ else go (sa, sb', Just x)+ Skip sb' -> go (sa, sb', Just x)+ Stop -> return False++{-# INLINE_NORMAL stripPrefix #-}+stripPrefix+ :: (Eq a, Monad m)+ => Stream m a -> Stream m a -> m (Maybe (Stream m a))+stripPrefix (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)+ where+ go (sa, sb, Nothing) = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go (sa', sb, Just x)+ Skip sa' -> go (sa', sb, Nothing)+ Stop -> return $ Just (Stream stepb sb)++ go (sa, sb, Just x) = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go (sa, sb', Nothing)+ else return Nothing+ Skip sb' -> go (sa, sb', Just x)+ Stop -> return Nothing++------------------------------------------------------------------------------+-- Map and Fold+------------------------------------------------------------------------------++-- | Execute a monadic action for each element of the 'Stream'+{-# INLINE_NORMAL mapM_ #-}+mapM_ :: Monad m => (a -> m b) -> Stream m a -> m ()+mapM_ m = drain . mapM m++-------------------------------------------------------------------------------+-- Stream transformations using Unfolds+-------------------------------------------------------------------------------++-- Define a unique structure to use in inspection testing+data ConcatMapUState o i =+ ConcatMapUOuter o+ | ConcatMapUInner o i++-- | @concatMapU unfold stream@ uses @unfold@ to map the input stream elements+-- to streams and then flattens the generated streams into a single output+-- stream.++-- This is like 'concatMap' but uses an unfold with an explicit state to+-- generate the stream instead of a 'Stream' type generator. This allows better+-- optimization via fusion. This can be many times more efficient than+-- 'concatMap'.++{-# INLINE_NORMAL concatMapU #-}+concatMapU :: Monad m => Unfold m a b -> Stream m a -> Stream m b+concatMapU (Unfold istep inject) (Stream ostep ost) =+ Stream step (ConcatMapUOuter ost)+ where+ {-# INLINE_LATE step #-}+ step gst (ConcatMapUOuter o) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (ConcatMapUInner o' i))+ Skip o' -> return $ Skip (ConcatMapUOuter o')+ Stop -> return $ Stop++ step _ (ConcatMapUInner o i) = do+ r <- istep i+ return $ case r of+ Yield x i' -> Yield x (ConcatMapUInner o i')+ Skip i' -> Skip (ConcatMapUInner o i')+ Stop -> Skip (ConcatMapUOuter o)++data ConcatUnfoldInterleaveState o i =+ ConcatUnfoldInterleaveOuter o [i]+ | ConcatUnfoldInterleaveInner o [i]+ | ConcatUnfoldInterleaveInnerL [i] [i]+ | ConcatUnfoldInterleaveInnerR [i] [i]++-- XXX use arrays to store state instead of lists.+-- XXX In general we can use different scheduling strategies e.g. how to+-- schedule the outer vs inner loop or assigning weights to different streams+-- or outer and inner loops.++-- After a yield, switch to the next stream. Do not switch streams on Skip.+-- Yield from outer stream switches to the inner stream.+--+-- There are two choices here, (1) exhaust the outer stream first and then+-- start yielding from the inner streams, this is much simpler to implement,+-- (2) yield at least one element from an inner stream before going back to+-- outer stream and opening the next stream from it.+--+-- Ideally, we need some scheduling bias to inner streams vs outer stream.+-- Maybe we can configure the behavior.+--+{-# INLINE_NORMAL concatUnfoldInterleave #-}+concatUnfoldInterleave :: Monad m => Unfold m a b -> Stream m a -> Stream m b+concatUnfoldInterleave (Unfold istep inject) (Stream ostep ost) =+ Stream step (ConcatUnfoldInterleaveOuter ost [])+ where+ {-# INLINE_LATE step #-}+ step gst (ConcatUnfoldInterleaveOuter o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (ConcatUnfoldInterleaveInner o' (i : ls)))+ Skip o' -> return $ Skip (ConcatUnfoldInterleaveOuter o' ls)+ Stop -> return $ Skip (ConcatUnfoldInterleaveInnerL ls [])++ step _ (ConcatUnfoldInterleaveInner _ []) = undefined+ step _ (ConcatUnfoldInterleaveInner o (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveOuter o (s:ls))+ Skip s -> Skip (ConcatUnfoldInterleaveInner o (s:ls))+ Stop -> Skip (ConcatUnfoldInterleaveOuter o ls)++ step _ (ConcatUnfoldInterleaveInnerL [] []) = return Stop+ step _ (ConcatUnfoldInterleaveInnerL [] rs) =+ return $ Skip (ConcatUnfoldInterleaveInnerR [] rs)++ step _ (ConcatUnfoldInterleaveInnerL (st:ls) rs) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveInnerL ls (s:rs))+ Skip s -> Skip (ConcatUnfoldInterleaveInnerL (s:ls) rs)+ Stop -> Skip (ConcatUnfoldInterleaveInnerL ls rs)++ step _ (ConcatUnfoldInterleaveInnerR [] []) = return Stop+ step _ (ConcatUnfoldInterleaveInnerR ls []) =+ return $ Skip (ConcatUnfoldInterleaveInnerL ls [])++ step _ (ConcatUnfoldInterleaveInnerR ls (st:rs)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveInnerR (s:ls) rs)+ Skip s -> Skip (ConcatUnfoldInterleaveInnerR ls (s:rs))+ Stop -> Skip (ConcatUnfoldInterleaveInnerR ls rs)++-- XXX In general we can use different scheduling strategies e.g. how to+-- schedule the outer vs inner loop or assigning weights to different streams+-- or outer and inner loops.+--+-- This could be inefficient if the tasks are too small.+--+-- Compared to concatUnfoldInterleave this one switches streams on Skips.+--+{-# INLINE_NORMAL concatUnfoldRoundrobin #-}+concatUnfoldRoundrobin :: Monad m => Unfold m a b -> Stream m a -> Stream m b+concatUnfoldRoundrobin (Unfold istep inject) (Stream ostep ost) =+ Stream step (ConcatUnfoldInterleaveOuter ost [])+ where+ {-# INLINE_LATE step #-}+ step gst (ConcatUnfoldInterleaveOuter o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (ConcatUnfoldInterleaveInner o' (i : ls)))+ Skip o' -> return $ Skip (ConcatUnfoldInterleaveInner o' ls)+ Stop -> return $ Skip (ConcatUnfoldInterleaveInnerL ls [])++ step _ (ConcatUnfoldInterleaveInner o []) =+ return $ Skip (ConcatUnfoldInterleaveOuter o [])++ step _ (ConcatUnfoldInterleaveInner o (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveOuter o (s:ls))+ Skip s -> Skip (ConcatUnfoldInterleaveOuter o (s:ls))+ Stop -> Skip (ConcatUnfoldInterleaveOuter o ls)++ step _ (ConcatUnfoldInterleaveInnerL [] []) = return Stop+ step _ (ConcatUnfoldInterleaveInnerL [] rs) =+ return $ Skip (ConcatUnfoldInterleaveInnerR [] rs)++ step _ (ConcatUnfoldInterleaveInnerL (st:ls) rs) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveInnerL ls (s:rs))+ Skip s -> Skip (ConcatUnfoldInterleaveInnerL ls (s:rs))+ Stop -> Skip (ConcatUnfoldInterleaveInnerL ls rs)++ step _ (ConcatUnfoldInterleaveInnerR [] []) = return Stop+ step _ (ConcatUnfoldInterleaveInnerR ls []) =+ return $ Skip (ConcatUnfoldInterleaveInnerL ls [])++ step _ (ConcatUnfoldInterleaveInnerR ls (st:rs)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (ConcatUnfoldInterleaveInnerR (s:ls) rs)+ Skip s -> Skip (ConcatUnfoldInterleaveInnerR (s:ls) rs)+ Stop -> Skip (ConcatUnfoldInterleaveInnerR ls rs)++data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2++-- Note that this could be much faster compared to the CPS stream. However, as+-- the number of streams being composed increases this may become expensive.+-- Need to see where the breaking point is between the two.+--+{-# INLINE_NORMAL append #-}+append :: Monad m => Stream m a -> Stream m a -> Stream m a+append (Stream step1 state1) (Stream step2 state2) =+ Stream step (AppendFirst state1)++ where++ {-# INLINE_LATE step #-}+ step gst (AppendFirst st) = do+ r <- step1 gst st+ return $ case r of+ Yield a s -> Yield a (AppendFirst s)+ Skip s -> Skip (AppendFirst s)+ Stop -> Skip (AppendSecond state2)++ step gst (AppendSecond st) = do+ r <- step2 gst st+ return $ case r of+ Yield a s -> Yield a (AppendSecond s)+ Skip s -> Skip (AppendSecond s)+ Stop -> Stop++data InterleaveState s1 s2 = InterleaveFirst s1 s2 | InterleaveSecond s1 s2+ | InterleaveSecondOnly s2 | InterleaveFirstOnly s1++{-# INLINE_NORMAL interleave #-}+interleave :: Monad m => Stream m a -> Stream m a -> Stream m a+interleave (Stream step1 state1) (Stream step2 state2) =+ Stream step (InterleaveFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (InterleaveFirst st1 st2) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveSecond s st2)+ Skip s -> Skip (InterleaveFirst s st2)+ Stop -> Skip (InterleaveSecondOnly st2)++ step gst (InterleaveSecond st1 st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveFirst st1 s)+ Skip s -> Skip (InterleaveSecond st1 s)+ Stop -> Skip (InterleaveFirstOnly st1)++ step gst (InterleaveFirstOnly st1) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveFirstOnly s)+ Skip s -> Skip (InterleaveFirstOnly s)+ Stop -> Stop++ step gst (InterleaveSecondOnly st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveSecondOnly s)+ Skip s -> Skip (InterleaveSecondOnly s)+ Stop -> Stop++{-# INLINE_NORMAL interleaveMin #-}+interleaveMin :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveMin (Stream step1 state1) (Stream step2 state2) =+ Stream step (InterleaveFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (InterleaveFirst st1 st2) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveSecond s st2)+ Skip s -> Skip (InterleaveFirst s st2)+ Stop -> Stop++ step gst (InterleaveSecond st1 st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveFirst st1 s)+ Skip s -> Skip (InterleaveSecond st1 s)+ Stop -> Stop++ step _ (InterleaveFirstOnly _) = undefined+ step _ (InterleaveSecondOnly _) = undefined++{-# INLINE_NORMAL interleaveSuffix #-}+interleaveSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveSuffix (Stream step1 state1) (Stream step2 state2) =+ Stream step (InterleaveFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (InterleaveFirst st1 st2) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveSecond s st2)+ Skip s -> Skip (InterleaveFirst s st2)+ Stop -> Stop++ step gst (InterleaveSecond st1 st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveFirst st1 s)+ Skip s -> Skip (InterleaveSecond st1 s)+ Stop -> Skip (InterleaveFirstOnly st1)++ step gst (InterleaveFirstOnly st1) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveFirstOnly s)+ Skip s -> Skip (InterleaveFirstOnly s)+ Stop -> Stop++ step _ (InterleaveSecondOnly _) = undefined++data InterleaveInfixState s1 s2 a+ = InterleaveInfixFirst s1 s2+ | InterleaveInfixSecondBuf s1 s2+ | InterleaveInfixSecondYield s1 s2 a+ | InterleaveInfixFirstYield s1 s2 a+ | InterleaveInfixFirstOnly s1++{-# INLINE_NORMAL interleaveInfix #-}+interleaveInfix :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveInfix (Stream step1 state1) (Stream step2 state2) =+ Stream step (InterleaveInfixFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (InterleaveInfixFirst st1 st2) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveInfixSecondBuf s st2)+ Skip s -> Skip (InterleaveInfixFirst s st2)+ Stop -> Stop++ step gst (InterleaveInfixSecondBuf st1 st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Skip (InterleaveInfixSecondYield st1 s a)+ Skip s -> Skip (InterleaveInfixSecondBuf st1 s)+ Stop -> Skip (InterleaveInfixFirstOnly st1)++ step gst (InterleaveInfixSecondYield st1 st2 x) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield x (InterleaveInfixFirstYield s st2 a)+ Skip s -> Skip (InterleaveInfixSecondYield s st2 x)+ Stop -> Stop++ step _ (InterleaveInfixFirstYield st1 st2 x) = do+ return $ Yield x (InterleaveInfixSecondBuf st1 st2)++ step gst (InterleaveInfixFirstOnly st1) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveInfixFirstOnly s)+ Skip s -> Skip (InterleaveInfixFirstOnly s)+ Stop -> Stop++{-# INLINE_NORMAL roundRobin #-}+roundRobin :: Monad m => Stream m a -> Stream m a -> Stream m a+roundRobin (Stream step1 state1) (Stream step2 state2) =+ Stream step (InterleaveFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (InterleaveFirst st1 st2) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveSecond s st2)+ Skip s -> Skip (InterleaveSecond s st2)+ Stop -> Skip (InterleaveSecondOnly st2)++ step gst (InterleaveSecond st1 st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveFirst st1 s)+ Skip s -> Skip (InterleaveFirst st1 s)+ Stop -> Skip (InterleaveFirstOnly st1)++ step gst (InterleaveSecondOnly st2) = do+ r <- step2 gst st2+ return $ case r of+ Yield a s -> Yield a (InterleaveSecondOnly s)+ Skip s -> Skip (InterleaveSecondOnly s)+ Stop -> Stop++ step gst (InterleaveFirstOnly st1) = do+ r <- step1 gst st1+ return $ case r of+ Yield a s -> Yield a (InterleaveFirstOnly s)+ Skip s -> Skip (InterleaveFirstOnly s)+ Stop -> Stop++data ICUState s1 s2 i1 i2 =+ ICUFirst s1 s2+ | ICUSecond s1 s2+ | ICUSecondOnly s2+ | ICUFirstOnly s1+ | ICUFirstInner s1 s2 i1+ | ICUSecondInner s1 s2 i2+ | ICUFirstOnlyInner s1 i1+ | ICUSecondOnlyInner s2 i2++-- | Interleave streams (full streams, not the elements) unfolded from two+-- input streams and concat. Stop when the first stream stops. If the second+-- stream ends before the first one then first stream still keeps running alone+-- without any interleaving with the second stream.+--+-- [a1, a2, ... an] [b1, b2 ...]+-- => [streamA1, streamA2, ... streamAn] [streamB1, streamB2, ...]+-- => [streamA1, streamB1, streamA2...StreamAn, streamBn]+-- => [a11, a12, ...a1j, b11, b12, ...b1k, a21, a22, ...]+--+{-# INLINE_NORMAL gintercalateSuffix #-}+gintercalateSuffix+ :: Monad m+ => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+gintercalateSuffix+ (Unfold istep1 inject1) (Stream step1 state1)+ (Unfold istep2 inject2) (Stream step2 state2) =+ Stream step (ICUFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (ICUFirst s1 s2) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (ICUFirstInner s s2 i))+ Skip s -> return $ Skip (ICUFirst s s2)+ Stop -> return Stop++ step gst (ICUFirstOnly s1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (ICUFirstOnlyInner s i))+ Skip s -> return $ Skip (ICUFirstOnly s)+ Stop -> return Stop++ step _ (ICUFirstInner s1 s2 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (ICUFirstInner s1 s2 i')+ Skip i' -> Skip (ICUFirstInner s1 s2 i')+ Stop -> Skip (ICUSecond s1 s2)++ step _ (ICUFirstOnlyInner s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (ICUFirstOnlyInner s1 i')+ Skip i' -> Skip (ICUFirstOnlyInner s1 i')+ Stop -> Skip (ICUFirstOnly s1)++ step gst (ICUSecond s1 s2) = do+ r <- step2 (adaptState gst) s2+ case r of+ Yield a s -> do+ i <- inject2 a+ i `seq` return (Skip (ICUSecondInner s1 s i))+ Skip s -> return $ Skip (ICUSecond s1 s)+ Stop -> return $ Skip (ICUFirstOnly s1)++ step _ (ICUSecondInner s1 s2 i2) = do+ r <- istep2 i2+ return $ case r of+ Yield x i' -> Yield x (ICUSecondInner s1 s2 i')+ Skip i' -> Skip (ICUSecondInner s1 s2 i')+ Stop -> Skip (ICUFirst s1 s2)++ step _ (ICUSecondOnly _s2) = undefined+ step _ (ICUSecondOnlyInner _s2 _i2) = undefined++data InterposeSuffixState s1 i1 =+ InterposeSuffixFirst s1+ -- | InterposeSuffixFirstYield s1 i1+ | InterposeSuffixFirstInner s1 i1+ | InterposeSuffixSecond s1++-- Note that if an unfolded layer turns out to be nil we still emit the+-- separator effect. An alternate behavior could be to emit the separator+-- effect only if at least one element has been yielded by the unfolding.+-- However, that becomes a bit complicated, so we have chosen the former+-- behvaior for now.+{-# INLINE_NORMAL interposeSuffix #-}+interposeSuffix+ :: Monad m+ => m c -> Unfold m b c -> Stream m b -> Stream m c+interposeSuffix+ action+ (Unfold istep1 inject1) (Stream step1 state1) =+ Stream step (InterposeSuffixFirst state1)++ where++ {-# INLINE_LATE step #-}+ step gst (InterposeSuffixFirst s1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (InterposeSuffixFirstInner s i))+ -- i `seq` return (Skip (InterposeSuffixFirstYield s i))+ Skip s -> return $ Skip (InterposeSuffixFirst s)+ Stop -> return Stop++ {-+ step _ (InterposeSuffixFirstYield s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (InterposeSuffixFirstInner s1 i')+ Skip i' -> Skip (InterposeSuffixFirstYield s1 i')+ Stop -> Skip (InterposeSuffixFirst s1)+ -}++ step _ (InterposeSuffixFirstInner s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (InterposeSuffixFirstInner s1 i')+ Skip i' -> Skip (InterposeSuffixFirstInner s1 i')+ Stop -> Skip (InterposeSuffixSecond s1)++ step _ (InterposeSuffixSecond s1) = do+ r <- action+ return $ Yield r (InterposeSuffixFirst s1)++data ICALState s1 s2 i1 i2 a =+ ICALFirst s1 s2+ -- | ICALFirstYield s1 s2 i1+ | ICALFirstInner s1 s2 i1+ | ICALFirstOnly s1+ | ICALFirstOnlyInner s1 i1+ | ICALSecondInject s1 s2+ | ICALFirstInject s1 s2 i2+ -- | ICALFirstBuf s1 s2 i1 i2+ | ICALSecondInner s1 s2 i1 i2+ -- -- | ICALSecondInner s1 s2 i1 i2 a+ -- -- | ICALFirstResume s1 s2 i1 i2 a++-- | Interleave streams (full streams, not the elements) unfolded from two+-- input streams and concat. Stop when the first stream stops. If the second+-- stream ends before the first one then first stream still keeps running alone+-- without any interleaving with the second stream.+--+-- [a1, a2, ... an] [b1, b2 ...]+-- => [streamA1, streamA2, ... streamAn] [streamB1, streamB2, ...]+-- => [streamA1, streamB1, streamA2...StreamAn, streamBn]+-- => [a11, a12, ...a1j, b11, b12, ...b1k, a21, a22, ...]+--+{-# INLINE_NORMAL gintercalate #-}+gintercalate+ :: Monad m+ => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+gintercalate+ (Unfold istep1 inject1) (Stream step1 state1)+ (Unfold istep2 inject2) (Stream step2 state2) =+ Stream step (ICALFirst state1 state2)++ where++ {-# INLINE_LATE step #-}+ step gst (ICALFirst s1 s2) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (ICALFirstInner s s2 i))+ -- i `seq` return (Skip (ICALFirstYield s s2 i))+ Skip s -> return $ Skip (ICALFirst s s2)+ Stop -> return Stop++ {-+ step _ (ICALFirstYield s1 s2 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (ICALFirstInner s1 s2 i')+ Skip i' -> Skip (ICALFirstYield s1 s2 i')+ Stop -> Skip (ICALFirst s1 s2)+ -}++ step _ (ICALFirstInner s1 s2 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (ICALFirstInner s1 s2 i')+ Skip i' -> Skip (ICALFirstInner s1 s2 i')+ Stop -> Skip (ICALSecondInject s1 s2)++ step gst (ICALFirstOnly s1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (ICALFirstOnlyInner s i))+ Skip s -> return $ Skip (ICALFirstOnly s)+ Stop -> return Stop++ step _ (ICALFirstOnlyInner s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (ICALFirstOnlyInner s1 i')+ Skip i' -> Skip (ICALFirstOnlyInner s1 i')+ Stop -> Skip (ICALFirstOnly s1)++ -- We inject the second stream even before checking if the first stream+ -- would yield any more elements. There is no clear choice whether we+ -- should do this before or after that. Doing it after may make the state+ -- machine a bit simpler though.+ step gst (ICALSecondInject s1 s2) = do+ r <- step2 (adaptState gst) s2+ case r of+ Yield a s -> do+ i <- inject2 a+ i `seq` return (Skip (ICALFirstInject s1 s i))+ Skip s -> return $ Skip (ICALSecondInject s1 s)+ Stop -> return $ Skip (ICALFirstOnly s1)++ step gst (ICALFirstInject s1 s2 i2) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (ICALSecondInner s s2 i i2))+ -- i `seq` return (Skip (ICALFirstBuf s s2 i i2))+ Skip s -> return $ Skip (ICALFirstInject s s2 i2)+ Stop -> return Stop++ {-+ step _ (ICALFirstBuf s1 s2 i1 i2) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Skip (ICALSecondInner s1 s2 i' i2 x)+ Skip i' -> Skip (ICALFirstBuf s1 s2 i' i2)+ Stop -> Stop++ step _ (ICALSecondInner s1 s2 i1 i2 v) = do+ r <- istep2 i2+ return $ case r of+ Yield x i' -> Yield x (ICALSecondInner s1 s2 i1 i' v)+ Skip i' -> Skip (ICALSecondInner s1 s2 i1 i' v)+ Stop -> Skip (ICALFirstResume s1 s2 i1 i2 v)+ -}++ step _ (ICALSecondInner s1 s2 i1 i2) = do+ r <- istep2 i2+ return $ case r of+ Yield x i' -> Yield x (ICALSecondInner s1 s2 i1 i')+ Skip i' -> Skip (ICALSecondInner s1 s2 i1 i')+ Stop -> Skip (ICALFirstInner s1 s2 i1)+ -- Stop -> Skip (ICALFirstResume s1 s2 i1 i2)++ {-+ step _ (ICALFirstResume s1 s2 i1 i2 x) = do+ return $ Yield x (ICALFirstInner s1 s2 i1 i2)+ -}++data InterposeState s1 i1 a =+ InterposeFirst s1+ -- | InterposeFirstYield s1 i1+ | InterposeFirstInner s1 i1+ | InterposeFirstInject s1+ -- | InterposeFirstBuf s1 i1+ | InterposeSecondYield s1 i1+ -- -- | InterposeSecondYield s1 i1 a+ -- -- | InterposeFirstResume s1 i1 a++-- Note that this only interposes the pure values, we may run many effects to+-- generate those values as some effects may not generate anything (Skip).+{-# INLINE_NORMAL interpose #-}+interpose :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+interpose+ action+ (Unfold istep1 inject1) (Stream step1 state1) =+ Stream step (InterposeFirst state1)++ where++ {-# INLINE_LATE step #-}+ step gst (InterposeFirst s1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ i `seq` return (Skip (InterposeFirstInner s i))+ -- i `seq` return (Skip (InterposeFirstYield s i))+ Skip s -> return $ Skip (InterposeFirst s)+ Stop -> return Stop++ {-+ step _ (InterposeFirstYield s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (InterposeFirstInner s1 i')+ Skip i' -> Skip (InterposeFirstYield s1 i')+ Stop -> Skip (InterposeFirst s1)+ -}++ step _ (InterposeFirstInner s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Yield x (InterposeFirstInner s1 i')+ Skip i' -> Skip (InterposeFirstInner s1 i')+ Stop -> Skip (InterposeFirstInject s1)++ step gst (InterposeFirstInject s1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield a s -> do+ i <- inject1 a+ -- i `seq` return (Skip (InterposeFirstBuf s i))+ i `seq` return (Skip (InterposeSecondYield s i))+ Skip s -> return $ Skip (InterposeFirstInject s)+ Stop -> return Stop++ {-+ step _ (InterposeFirstBuf s1 i1) = do+ r <- istep1 i1+ return $ case r of+ Yield x i' -> Skip (InterposeSecondYield s1 i' x)+ Skip i' -> Skip (InterposeFirstBuf s1 i')+ Stop -> Stop+ -}++ {-+ step _ (InterposeSecondYield s1 i1 v) = do+ r <- action+ return $ Yield r (InterposeFirstResume s1 i1 v)+ -}+ step _ (InterposeSecondYield s1 i1) = do+ r <- action+ return $ Yield r (InterposeFirstInner s1 i1)++ {-+ step _ (InterposeFirstResume s1 i1 v) = do+ return $ Yield v (InterposeFirstInner s1 i1)+ -}++------------------------------------------------------------------------------+-- Exceptions+------------------------------------------------------------------------------++data GbracketState s1 s2 v+ = GBracketInit+ | GBracketNormal s1 v+ | GBracketException s2++-- | The most general bracketing and exception combinator. All other+-- combinators can be expressed in terms of this combinator. This can also be+-- used for cases which are not covered by the standard combinators.+--+-- /Internal/+--+{-# INLINE_NORMAL gbracket #-}+gbracket+ :: Monad m+ => m c -- ^ before+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)+ -> (c -> m d) -- ^ after, on normal stop+ -> (c -> e -> Stream m b) -- ^ on exception+ -> (c -> Stream m b) -- ^ stream generator+ -> Stream m b+gbracket bef exc aft fexc fnormal =+ Stream step GBracketInit++ where++ {-# INLINE_LATE step #-}+ step _ GBracketInit = do+ r <- bef+ return $ Skip $ GBracketNormal (fnormal r) r++ step gst (GBracketNormal (UnStream step1 st) v) = do+ res <- exc $ step1 gst st+ case res of+ Right r -> case r of+ Yield x s ->+ return $ Yield x (GBracketNormal (Stream step1 s) v)+ Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)+ Stop -> aft v >> return Stop+ Left e -> return $ Skip (GBracketException (fexc v e))+ step gst (GBracketException (UnStream step1 st)) = do+ res <- step1 gst st+ case res of+ Yield x s -> return $ Yield x (GBracketException (Stream step1 s))+ Skip s -> return $ Skip (GBracketException (Stream step1 s))+ Stop -> return Stop++-- | Create an IORef holding a finalizer that is called automatically when the+-- IORef is garbage collected. The IORef can be written to with a 'Nothing'+-- value to deactivate the finalizer.+newFinalizedIORef :: (MonadIO m, MonadBaseControl IO m)+ => m a -> m (IORef (Maybe (IO ())))+newFinalizedIORef finalizer = do+ mrun <- captureMonadState+ ref <- liftIO $ newIORef $ Just $ liftIO $ void $ do+ _ <- runInIO mrun finalizer+ return ()+ let finalizer1 = do+ res <- readIORef ref+ case res of+ Nothing -> return ()+ Just f -> f+ _ <- liftIO $ mkWeakIORef ref finalizer1+ return ref++-- | Run the finalizer stored in an IORef and deactivate it so that it is run+-- only once.+--+runIORefFinalizer :: MonadIO m => IORef (Maybe (IO ())) -> m ()+runIORefFinalizer ref = liftIO $ do+ res <- readIORef ref+ case res of+ Nothing -> return ()+ Just f -> writeIORef ref Nothing >> f++-- | Deactivate the finalizer stored in an IORef without running it.+--+clearIORefFinalizer :: MonadIO m => IORef (Maybe (IO ())) -> m ()+clearIORefFinalizer ref = liftIO $ writeIORef ref Nothing++data GbracketIOState s1 s2 v wref+ = GBracketIOInit+ | GBracketIONormal s1 v wref+ | GBracketIOException s2++-- | Like gbracket but also uses a finalizer to make sure when the stream is+-- garbage collected we run the finalizing action. This requires a MonadIO and+-- MonadBaseControl IO constraint.+--+-- | The most general bracketing and exception combinator. All other+-- combinators can be expressed in terms of this combinator. This can also be+-- used for cases which are not covered by the standard combinators.+--+-- /Internal/+--+{-# INLINE_NORMAL gbracketIO #-}+gbracketIO+ :: (MonadIO m, MonadBaseControl IO m)+ => m c -- ^ before+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)+ -> (c -> m d) -- ^ after, on normal stop or GC+ -> (c -> e -> Stream m b) -- ^ on exception+ -> (c -> Stream m b) -- ^ stream generator+ -> Stream m b+gbracketIO bef exc aft fexc fnormal =+ Stream step GBracketIOInit++ where++ -- If the stream is never evaluated the "aft" action will never be+ -- called. For that to occur we will need the user of this API to pass a+ -- weak pointer to us.+ {-# INLINE_LATE step #-}+ step _ GBracketIOInit = do+ r <- bef+ ref <- newFinalizedIORef (aft r)+ return $ Skip $ GBracketIONormal (fnormal r) r ref++ step gst (GBracketIONormal (UnStream step1 st) v ref) = do+ res <- exc $ step1 gst st+ case res of+ Right r -> case r of+ Yield x s ->+ return $ Yield x (GBracketIONormal (Stream step1 s) v ref)+ Skip s ->+ return $ Skip (GBracketIONormal (Stream step1 s) v ref)+ Stop -> do+ runIORefFinalizer ref+ return Stop+ Left e -> do+ clearIORefFinalizer ref+ return $ Skip (GBracketIOException (fexc v e))+ step gst (GBracketIOException (UnStream step1 st)) = do+ res <- step1 gst st+ case res of+ Yield x s ->+ return $ Yield x (GBracketIOException (Stream step1 s))+ Skip s -> return $ Skip (GBracketIOException (Stream step1 s))+ Stop -> return Stop++-- | Run a side effect before the stream yields its first element.+{-# INLINE_NORMAL before #-}+before :: Monad m => m b -> Stream m a -> Stream m a+before action (Stream step state) = Stream step' Nothing++ where++ {-# INLINE_LATE step' #-}+ step' _ Nothing = action >> return (Skip (Just state))++ step' gst (Just st) = do+ res <- step gst st+ case res of+ Yield x s -> return $ Yield x (Just s)+ Skip s -> return $ Skip (Just s)+ Stop -> return Stop++-- | Run a side effect whenever the stream stops normally.+{-# INLINE_NORMAL after #-}+after :: Monad m => m b -> Stream m a -> Stream m a+after action (Stream step state) = Stream step' state++ where++ {-# INLINE_LATE step' #-}+ step' gst st = do+ res <- step gst st+ case res of+ Yield x s -> return $ Yield x s+ Skip s -> return $ Skip s+ Stop -> action >> return Stop++{-# INLINE_NORMAL afterIO #-}+afterIO :: (MonadIO m, MonadBaseControl IO m)+ => m b -> Stream m a -> Stream m a+afterIO action (Stream step state) = Stream step' Nothing++ where++ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ ref <- newFinalizedIORef action+ return $ Skip $ Just (state, ref)+ step' gst (Just (st, ref)) = do+ res <- step gst st+ case res of+ Yield x s -> return $ Yield x (Just (s, ref))+ Skip s -> return $ Skip (Just (s, ref))+ Stop -> do+ runIORefFinalizer ref+ return Stop++-- XXX These combinators are expensive due to the call to+-- onException/handle/try on each step. Therefore, when possible, they should+-- be called in an outer loop where we perform less iterations. For example, we+-- cannot call them on each iteration in a char stream, instead we can call+-- them when doing an IO on an array.+--+-- XXX For high performance error checks in busy streams we may need another+-- Error constructor in step.+--+-- | Run a side effect whenever the stream aborts due to an exception. The+-- exception is not caught, simply rethrown.+{-# INLINE_NORMAL onException #-}+onException :: MonadCatch m => m b -> Stream m a -> Stream m a+onException action str =+ gbracket (return ()) MC.try return+ (\_ (e :: MC.SomeException) -> nilM (action >> MC.throwM e))+ (\_ -> str)++{-# INLINE_NORMAL _onException #-}+_onException :: MonadCatch m => m b -> Stream m a -> Stream m a+_onException action (Stream step state) = Stream step' state++ where++ {-# INLINE_LATE step' #-}+ step' gst st = do+ res <- step gst st `MC.onException` action+ case res of+ Yield x s -> return $ Yield x s+ Skip s -> return $ Skip s+ Stop -> return Stop++-- XXX bracket is like concatMap, it generates a stream and then flattens it.+-- Like concatMap it has 10x worse performance compared to linear fused+-- compositions.+--+-- | Run the first action before the stream starts and remember its output,+-- generate a stream using the output, run the second action providing the+-- remembered value as an argument whenever the stream ends normally or due to+-- an exception.+{-# INLINE_NORMAL bracket #-}+bracket :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a+bracket bef aft bet =+ gbracket bef MC.try aft+ (\a (e :: SomeException) -> nilM (aft a >> MC.throwM e)) bet++{-# INLINE_NORMAL bracketIO #-}+bracketIO :: (MonadAsync m, MonadCatch m)+ => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a+bracketIO bef aft bet =+ gbracketIO bef MC.try aft+ (\a (e :: SomeException) -> nilM (aft a >> MC.throwM e)) bet++data BracketState s v = BracketInit | BracketRun s v++{-# INLINE_NORMAL _bracket #-}+_bracket :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a+_bracket bef aft bet = Stream step' BracketInit++ where++ {-# INLINE_LATE step' #-}+ step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))++ -- NOTE: It is important to use UnStream instead of the Stream pattern+ -- here, otherwise we get huge perf degradation, see note in concatMap.+ step' gst (BracketRun (UnStream step state) v) = do+ -- res <- step gst state `MC.onException` aft v+ res <- MC.try $ step gst state+ case res of+ Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop+ Right r -> case r of+ Yield x s -> return $ Yield x (BracketRun (Stream step s) v)+ Skip s -> return $ Skip (BracketRun (Stream step s) v)+ Stop -> aft v >> return Stop++-- | Run a side effect whenever the stream stops normally or aborts due to an+-- exception.+{-# INLINE finally #-}+finally :: MonadCatch m => m b -> Stream m a -> Stream m a+-- finally action xs = after action $ onException action xs+finally action xs = bracket (return ()) (\_ -> action) (const xs)++{-# INLINE finallyIO #-}+finallyIO :: (MonadAsync m, MonadCatch m) => m b -> Stream m a -> Stream m a+finallyIO action xs = bracketIO (return ()) (\_ -> action) (const xs)++-- | When evaluating a stream if an exception occurs, stream evaluation aborts+-- and the specified exception handler is run with the exception as argument.+{-# INLINE_NORMAL handle #-}+handle :: (MonadCatch m, Exception e)+ => (e -> Stream m a) -> Stream m a -> Stream m a+handle f str =+ gbracket (return ()) MC.try return (\_ e -> f e) (\_ -> str)++{-# INLINE_NORMAL _handle #-}+_handle :: (MonadCatch m, Exception e)+ => (e -> Stream m a) -> Stream m a -> Stream m a+_handle f (Stream step state) = Stream step' (Left state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (Left st) = do+ res <- MC.try $ step gst st+ case res of+ Left e -> return $ Skip $ Right (f e)+ Right r -> case r of+ Yield x s -> return $ Yield x (Left s)+ Skip s -> return $ Skip (Left s)+ Stop -> return Stop++ step' gst (Right (UnStream step1 st)) = do+ res <- step1 gst st+ case res of+ Yield x s -> return $ Yield x (Right (Stream step1 s))+ Skip s -> return $ Skip (Right (Stream step1 s))+ Stop -> return Stop++-------------------------------------------------------------------------------+-- General transformation+-------------------------------------------------------------------------------++{-# INLINE_NORMAL transform #-}+transform :: Monad m => Pipe m a b -> Stream m a -> Stream m b+transform (Pipe pstep1 pstep2 pstate) (Stream step state) =+ Stream step' (Consume pstate, state)++ where++ {-# INLINE_LATE step' #-}++ step' gst (Consume pst, st) = pst `seq` do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ res <- pstep1 pst x+ case res of+ Pipe.Yield b pst' -> return $ Yield b (pst', s)+ Pipe.Continue pst' -> return $ Skip (pst', s)+ Skip s -> return $ Skip (Consume pst, s)+ Stop -> return Stop++ step' _ (Produce pst, st) = pst `seq` do+ res <- pstep2 pst+ case res of+ Pipe.Yield b pst' -> return $ Yield b (pst', st)+ Pipe.Continue pst' -> return $ Skip (pst', st)++------------------------------------------------------------------------------+-- Transformation by Folding (Scans)+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Prescans+------------------------------------------------------------------------------++-- XXX Is a prescan useful, discarding the last step does not sound useful? I+-- am not sure about the utility of this function, so this is implemented but+-- not exposed. We can expose it if someone provides good reasons why this is+-- useful.+--+-- XXX We have to execute the stream one step ahead to know that we are at the+-- last step. The vector implementation of prescan executes the last fold step+-- but does not yield the result. This means we have executed the effect but+-- discarded value. This does not sound right. In this implementation we are+-- not executing the last fold step.+{-# INLINE_NORMAL prescanlM' #-}+prescanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+prescanlM' f mz (Stream step state) = Stream step' (state, mz)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, prev) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ acc <- prev+ return $ Yield acc (s, f acc x)+ Skip s -> return $ Skip (s, prev)+ Stop -> return Stop++{-# INLINE prescanl' #-}+prescanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+prescanl' f z = prescanlM' (\a b -> return (f a b)) (return z)++------------------------------------------------------------------------------+-- Monolithic postscans (postscan followed by a map)+------------------------------------------------------------------------------++-- The performance of a modular postscan followed by a map seems to be+-- equivalent to this monolithic scan followed by map therefore we may not need+-- this implementation. We just have it for performance comparison and in case+-- modular version does not perform well in some situation.+--+{-# INLINE_NORMAL postscanlMx' #-}+postscanlMx' :: Monad m+ => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b+postscanlMx' fstep begin done (Stream step state) = do+ Stream step' (state, begin)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, acc) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ old <- acc+ y <- fstep old x+ v <- done y+ v `seq` y `seq` return (Yield v (s, return y))+ Skip s -> return $ Skip (s, acc)+ Stop -> return Stop++{-# INLINE_NORMAL postscanlx' #-}+postscanlx' :: Monad m+ => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b+postscanlx' fstep begin done s =+ postscanlMx' (\b a -> return (fstep b a)) (return begin) (return . done) s++-- XXX do we need consM strict to evaluate the begin value?+{-# INLINE scanlMx' #-}+scanlMx' :: Monad m+ => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b+scanlMx' fstep begin done s =+ (begin >>= \x -> x `seq` done x) `consM` postscanlMx' fstep begin done s++{-# INLINE scanlx' #-}+scanlx' :: Monad m+ => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b+scanlx' fstep begin done s =+ scanlMx' (\b a -> return (fstep b a)) (return begin) (return . done) s++------------------------------------------------------------------------------+-- postscans+------------------------------------------------------------------------------++{-# INLINE_NORMAL postscanlM' #-}+postscanlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b+postscanlM' fstep begin (Stream step state) =+ begin `seq` Stream step' (state, begin)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, acc) = acc `seq` do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ y <- fstep acc x+ y `seq` return (Yield y (s, y))+ Skip s -> return $ Skip (s, acc)+ Stop -> return Stop++{-# INLINE_NORMAL postscanl' #-}+postscanl' :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+postscanl' f = postscanlM' (\a b -> return (f a b))++{-# INLINE_NORMAL postscanlM #-}+postscanlM :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b+postscanlM fstep begin (Stream step state) = Stream step' (state, begin)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, acc) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ y <- fstep acc x+ return (Yield y (s, y))+ Skip s -> return $ Skip (s, acc)+ Stop -> return Stop++{-# INLINE_NORMAL postscanl #-}+postscanl :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+postscanl f = postscanlM (\a b -> return (f a b))++{-# INLINE_NORMAL scanlM' #-}+scanlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b+scanlM' fstep begin s = begin `seq` (begin `cons` postscanlM' fstep begin s)++{-# INLINE scanl' #-}+scanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+scanl' f = scanlM' (\a b -> return (f a b))++{-# INLINE_NORMAL scanlM #-}+scanlM :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b+scanlM fstep begin s = begin `cons` postscanlM fstep begin s++{-# INLINE scanl #-}+scanl :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+scanl f = scanlM (\a b -> return (f a b))++{-# INLINE_NORMAL scanl1M #-}+scanl1M :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a+scanl1M fstep (Stream step state) = Stream step' (state, Nothing)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, Nothing) = do+ r <- step gst st+ case r of+ Yield x s -> return $ Yield x (s, Just x)+ Skip s -> return $ Skip (s, Nothing)+ Stop -> return Stop++ step' gst (st, Just acc) = do+ r <- step gst st+ case r of+ Yield y s -> do+ z <- fstep acc y+ return $ Yield z (s, Just z)+ Skip s -> return $ Skip (s, Just acc)+ Stop -> return Stop++{-# INLINE scanl1 #-}+scanl1 :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a+scanl1 f = scanl1M (\x y -> return (f x y))++{-# INLINE_NORMAL scanl1M' #-}+scanl1M' :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a+scanl1M' fstep (Stream step state) = Stream step' (state, Nothing)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, Nothing) = do+ r <- step gst st+ case r of+ Yield x s -> x `seq` return $ Yield x (s, Just x)+ Skip s -> return $ Skip (s, Nothing)+ Stop -> return Stop++ step' gst (st, Just acc) = acc `seq` do+ r <- step gst st+ case r of+ Yield y s -> do+ z <- fstep acc y+ z `seq` return $ Yield z (s, Just z)+ Skip s -> return $ Skip (s, Just acc)+ Stop -> return Stop++{-# INLINE scanl1' #-}+scanl1' :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a+scanl1' f = scanl1M' (\x y -> return (f x y))++------------------------------------------------------------------------------+-- Stateful map/scan+------------------------------------------------------------------------------++data RollingMapState s a = RollingMapInit s | RollingMapGo s a++{-# INLINE rollingMapM #-}+rollingMapM :: Monad m => (a -> a -> m b) -> Stream m a -> Stream m b+rollingMapM f (Stream step1 state1) = Stream step (RollingMapInit state1)+ where+ step gst (RollingMapInit st) = do+ r <- step1 (adaptState gst) st+ return $ case r of+ Yield x s -> Skip $ RollingMapGo s x+ Skip s -> Skip $ RollingMapInit s+ Stop -> Stop++ step gst (RollingMapGo s1 x1) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield x s -> do+ !res <- f x x1+ return $ Yield res $ RollingMapGo s x+ Skip s -> return $ Skip $ RollingMapGo s x1+ Stop -> return $ Stop++{-# INLINE rollingMap #-}+rollingMap :: Monad m => (a -> a -> b) -> Stream m a -> Stream m b+rollingMap f = rollingMapM (\x y -> return $ f x y)++------------------------------------------------------------------------------+-- Tapping/Distributing+------------------------------------------------------------------------------++{-# INLINE tap #-}+tap :: Monad m => Fold m a b -> Stream m a -> Stream m a+tap (Fold fstep initial extract) (Stream step state) = Stream step' Nothing++ where++ step' _ Nothing = do+ r <- initial+ return $ Skip (Just (r, state))++ step' gst (Just (acc, st)) = acc `seq` do+ r <- step gst st+ case r of+ Yield x s -> do+ acc' <- fstep acc x+ return $ Yield x (Just (acc', s))+ Skip s -> return $ Skip (Just (acc, s))+ Stop -> do+ void $ extract acc+ return $ Stop++{-# INLINE_NORMAL tapOffsetEvery #-}+tapOffsetEvery :: Monad m+ => Int -> Int -> Fold m a b -> Stream m a -> Stream m a+tapOffsetEvery offset n (Fold fstep initial extract) (Stream step state) =+ Stream step' Nothing++ where++ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ r <- initial+ return $ Skip (Just (r, state, offset `mod` n))++ step' gst (Just (acc, st, count)) | count <= 0 = do+ r <- step gst st+ case r of+ Yield x s -> do+ !acc' <- fstep acc x+ return $ Yield x (Just (acc', s, n - 1))+ Skip s -> return $ Skip (Just (acc, s, count))+ Stop -> do+ void $ extract acc+ return $ Stop++ step' gst (Just (acc, st, count)) = do+ r <- step gst st+ case r of+ Yield x s -> return $ Yield x (Just (acc, s, count - 1))+ Skip s -> return $ Skip (Just (acc, s, count))+ Stop -> do+ void $ extract acc+ return $ Stop++{-# INLINE_NORMAL pollCounts #-}+pollCounts+ :: MonadAsync m+ => (a -> Bool)+ -> (Stream m Int -> Stream m Int)+ -> Fold m Int b+ -> Stream m a+ -> Stream m a+pollCounts predicate transf fld (Stream step state) = Stream step' Nothing+ where++ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ -- As long as we are using an "Int" for counts lockfree reads from+ -- Var should work correctly on both 32-bit and 64-bit machines.+ -- However, an Int on a 32-bit machine may overflow quickly.+ countVar <- liftIO $ newVar (0 :: Int)+ tid <- forkManaged+ $ void $ runFold fld+ $ transf $ fromPrimVar countVar+ return $ Skip (Just (countVar, tid, state))++ step' gst (Just (countVar, tid, st)) = do+ r <- step gst st+ case r of+ Yield x s -> do+ when (predicate x) $ liftIO $ modifyVar' countVar (+ 1)+ return $ Yield x (Just (countVar, tid, s))+ Skip s -> return $ Skip (Just (countVar, tid, s))+ Stop -> do+ liftIO $ killThread tid+ return Stop++{-# INLINE_NORMAL tapRate #-}+tapRate ::+ (MonadAsync m, MonadCatch m)+ => Double+ -> (Int -> m b)+ -> Stream m a+ -> Stream m a+tapRate samplingRate action (Stream step state) = Stream step' Nothing+ where+ {-# NOINLINE loop #-}+ loop countVar prev = do+ i <-+ MC.catch+ (do liftIO $ threadDelay (round $ samplingRate * 1000000)+ i <- liftIO $ readVar countVar+ let !diff = i - prev+ void $ action diff+ return i)+ (\(e :: AsyncException) -> do+ i <- liftIO $ readVar countVar+ let !diff = i - prev+ void $ action diff+ throwM (MC.toException e))+ loop countVar i++ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ countVar <- liftIO $ newVar 0+ tid <- fork $ loop countVar 0+ ref <- liftIO $ newIORef ()+ _ <- liftIO $ mkWeakIORef ref (killThread tid)+ return $ Skip (Just (countVar, tid, state, ref))++ step' gst (Just (countVar, tid, st, ref)) = do+ r <- step gst st+ case r of+ Yield x s -> do+ liftIO $ modifyVar' countVar (+ 1)+ return $ Yield x (Just (countVar, tid, s, ref))+ Skip s -> return $ Skip (Just (countVar, tid, s, ref))+ Stop -> do+ liftIO $ killThread tid+ return Stop+++-------------------------------------------------------------------------------+-- Filtering+-------------------------------------------------------------------------------++{-# INLINE_NORMAL takeWhileM #-}+takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+takeWhileM f (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- step gst st+ case r of+ Yield x s -> do+ b <- f x+ return $ if b then Yield x s else Stop+ Skip s -> return $ Skip s+ Stop -> return Stop++{-# INLINE takeWhile #-}+takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+takeWhile f = takeWhileM (return . f)++{-# INLINE_NORMAL drop #-}+drop :: Monad m => Int -> Stream m a -> Stream m a+drop n (Stream step state) = Stream step' (state, Just n)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, Just i)+ | i > 0 = do+ r <- step gst st+ return $+ case r of+ Yield _ s -> Skip (s, Just (i - 1))+ Skip s -> Skip (s, Just i)+ Stop -> Stop+ | otherwise = return $ Skip (st, Nothing)++ step' gst (st, Nothing) = do+ r <- step gst st+ return $+ case r of+ Yield x s -> Yield x (s, Nothing)+ Skip s -> Skip (s, Nothing)+ Stop -> Stop++data DropWhileState s a+ = DropWhileDrop s+ | DropWhileYield a s+ | DropWhileNext s++{-# INLINE_NORMAL dropWhileM #-}+dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+dropWhileM f (Stream step state) = Stream step' (DropWhileDrop state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (DropWhileDrop st) = do+ r <- step gst st+ case r of+ Yield x s -> do+ b <- f x+ if b+ then return $ Skip (DropWhileDrop s)+ else return $ Skip (DropWhileYield x s)+ Skip s -> return $ Skip (DropWhileDrop s)+ Stop -> return Stop++ step' gst (DropWhileNext st) = do+ r <- step gst st+ case r of+ Yield x s -> return $ Skip (DropWhileYield x s)+ Skip s -> return $ Skip (DropWhileNext s)+ Stop -> return Stop++ step' _ (DropWhileYield x st) = return $ Yield x (DropWhileNext st)++{-# INLINE dropWhile #-}+dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+dropWhile f = dropWhileM (return . f)++{-# INLINE_NORMAL filterM #-}+filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+filterM f (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- step gst st+ case r of+ Yield x s -> do+ b <- f x+ return $ if b+ then Yield x s+ else Skip s+ Skip s -> return $ Skip s+ Stop -> return Stop++{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+filter f = filterM (return . f)++{-# INLINE_NORMAL uniq #-}+uniq :: (Eq a, Monad m) => Stream m a -> Stream m a+uniq (Stream step state) = Stream step' (Nothing, state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (Nothing, st) = do+ r <- step gst st+ case r of+ Yield x s -> return $ Yield x (Just x, s)+ Skip s -> return $ Skip (Nothing, s)+ Stop -> return Stop+ step' gst (Just x, st) = do+ r <- step gst st+ case r of+ Yield y s | x == y -> return $ Skip (Just x, s)+ | otherwise -> return $ Yield y (Just y, s)+ Skip s -> return $ Skip (Just x, s)+ Stop -> return Stop++------------------------------------------------------------------------------+-- Transformation by Mapping+------------------------------------------------------------------------------++{-# INLINE_NORMAL sequence #-}+sequence :: Monad m => Stream m (m a) -> Stream m a+sequence (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> x >>= \a -> return (Yield a s)+ Skip s -> return $ Skip s+ Stop -> return Stop++------------------------------------------------------------------------------+-- Inserting+------------------------------------------------------------------------------++data LoopState x s = FirstYield s+ | InterspersingYield s+ | YieldAndCarry x s++{-# INLINE_NORMAL intersperseM #-}+intersperseM :: Monad m => m a -> Stream m a -> Stream m a+intersperseM m (Stream step state) = Stream step' (FirstYield state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (FirstYield st) = do+ r <- step gst st+ return $+ case r of+ Yield x s -> Skip (YieldAndCarry x s)+ Skip s -> Skip (FirstYield s)+ Stop -> Stop++ step' gst (InterspersingYield st) = do+ r <- step gst st+ case r of+ Yield x s -> do+ a <- m+ return $ Yield a (YieldAndCarry x s)+ Skip s -> return $ Skip $ InterspersingYield s+ Stop -> return Stop++ step' _ (YieldAndCarry x st) = return $ Yield x (InterspersingYield st)++data SuffixState s a+ = SuffixElem s+ | SuffixSuffix s+ | SuffixYield a (SuffixState s a)++{-# INLINE_NORMAL intersperseSuffix #-}+intersperseSuffix :: forall m a. Monad m => m a -> Stream m a -> Stream m a+intersperseSuffix action (Stream step state) = Stream step' (SuffixElem state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (SuffixElem st) = do+ r <- step gst st+ return $ case r of+ Yield x s -> Skip (SuffixYield x (SuffixSuffix s))+ Skip s -> Skip (SuffixElem s)+ Stop -> Stop++ step' _ (SuffixSuffix st) = do+ action >>= \r -> return $ Skip (SuffixYield r (SuffixElem st))++ step' _ (SuffixYield x next) = return $ Yield x next++data SuffixSpanState s a+ = SuffixSpanElem s Int+ | SuffixSpanSuffix s+ | SuffixSpanYield a (SuffixSpanState s a)+ | SuffixSpanLast+ | SuffixSpanStop++-- | intersperse after every n items+{-# INLINE_NORMAL intersperseSuffixBySpan #-}+intersperseSuffixBySpan :: forall m a. Monad m+ => Int -> m a -> Stream m a -> Stream m a+intersperseSuffixBySpan n action (Stream step state) =+ Stream step' (SuffixSpanElem state n)+ where+ {-# INLINE_LATE step' #-}+ step' gst (SuffixSpanElem st i) | i > 0 = do+ r <- step gst st+ return $ case r of+ Yield x s -> Skip (SuffixSpanYield x (SuffixSpanElem s (i - 1)))+ Skip s -> Skip (SuffixSpanElem s i)+ Stop -> if i == n then Stop else Skip SuffixSpanLast+ step' _ (SuffixSpanElem st _) = return $ Skip (SuffixSpanSuffix st)++ step' _ (SuffixSpanSuffix st) = do+ action >>= \r -> return $ Skip (SuffixSpanYield r (SuffixSpanElem st n))++ step' _ (SuffixSpanLast) = do+ action >>= \r -> return $ Skip (SuffixSpanYield r SuffixSpanStop)++ step' _ (SuffixSpanYield x next) = return $ Yield x next++ step' _ (SuffixSpanStop) = return Stop++{-# INLINE intersperse #-}+intersperse :: Monad m => a -> Stream m a -> Stream m a+intersperse a = intersperseM (return a)++{-# INLINE_NORMAL insertBy #-}+insertBy :: Monad m => (a -> a -> Ordering) -> a -> Stream m a -> Stream m a+insertBy cmp a (Stream step state) = Stream step' (state, False, Nothing)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, False, _) = do+ r <- step gst st+ case r of+ Yield x s -> case cmp a x of+ GT -> return $ Yield x (s, False, Nothing)+ _ -> return $ Yield a (s, True, Just x)+ Skip s -> return $ Skip (s, False, Nothing)+ Stop -> return $ Yield a (st, True, Nothing)++ step' _ (_, True, Nothing) = return Stop++ step' gst (st, True, Just prev) = do+ r <- step gst st+ case r of+ Yield x s -> return $ Yield prev (s, True, Just x)+ Skip s -> return $ Skip (s, True, Just prev)+ Stop -> return $ Yield prev (st, True, Nothing)++------------------------------------------------------------------------------+-- Deleting+------------------------------------------------------------------------------++{-# INLINE_NORMAL deleteBy #-}+deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a+deleteBy eq x (Stream step state) = Stream step' (state, False)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, False) = do+ r <- step gst st+ case r of+ Yield y s -> return $+ if eq x y then Skip (s, True) else Yield y (s, False)+ Skip s -> return $ Skip (s, False)+ Stop -> return Stop++ step' gst (st, True) = do+ r <- step gst st+ case r of+ Yield y s -> return $ Yield y (s, True)+ Skip s -> return $ Skip (s, True)+ Stop -> return Stop++------------------------------------------------------------------------------+-- Transformation by Map and Filter+------------------------------------------------------------------------------++-- XXX Will this always fuse properly?+{-# INLINE_NORMAL mapMaybe #-}+mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b+mapMaybe f = fmap fromJust . filter isJust . map f++{-# INLINE_NORMAL mapMaybeM #-}+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b+mapMaybeM f = fmap fromJust . filter isJust . mapM f++------------------------------------------------------------------------------+-- Zipping+------------------------------------------------------------------------------++{-# INLINE_NORMAL indexed #-}+indexed :: Monad m => Stream m a -> Stream m (Int, a)+indexed (Stream step state) = Stream step' (state, 0)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, i) = i `seq` do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> return $ Yield (i, x) (s, i+1)+ Skip s -> return $ Skip (s, i)+ Stop -> return Stop++{-# INLINE_NORMAL indexedR #-}+indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)+indexedR m (Stream step state) = Stream step' (state, m)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, i) = i `seq` do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> let i' = i - 1+ in return $ Yield (i, x) (s, i')+ Skip s -> return $ Skip (s, i)+ Stop -> return Stop++{-# INLINE_NORMAL zipWithM #-}+zipWithM :: Monad m+ => (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c+zipWithM f (Stream stepa ta) (Stream stepb tb) = Stream step (ta, tb, Nothing)+ where+ {-# INLINE_LATE step #-}+ step gst (sa, sb, Nothing) = do+ r <- stepa (adaptState gst) sa+ return $+ case r of+ Yield x sa' -> Skip (sa', sb, Just x)+ Skip sa' -> Skip (sa', sb, Nothing)+ Stop -> Stop++ step gst (sa, sb, Just x) = do+ r <- stepb (adaptState gst) sb+ case r of+ Yield y sb' -> do+ z <- f x y+ return $ Yield z (sa, sb', Nothing)+ Skip sb' -> return $ Skip (sa, sb', Just x)+ Stop -> return Stop++#if __GLASGOW_HASKELL__ >= 801+{-# RULES "zipWithM xs xs"+ forall f xs. zipWithM @Identity f xs xs = mapM (\x -> f x x) xs #-}+#endif++{-# INLINE zipWith #-}+zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+zipWith f = zipWithM (\a b -> return (f a b))++------------------------------------------------------------------------------+-- Merging+------------------------------------------------------------------------------++{-# INLINE_NORMAL mergeByM #-}+mergeByM+ :: (Monad m)+ => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+mergeByM cmp (Stream stepa ta) (Stream stepb tb) =+ Stream step (Just ta, Just tb, Nothing, Nothing)+ where+ {-# INLINE_LATE step #-}++ -- one of the values is missing, and the corresponding stream is running+ step gst (Just sa, sb, Nothing, b) = do+ r <- stepa gst sa+ return $ case r of+ Yield a sa' -> Skip (Just sa', sb, Just a, b)+ Skip sa' -> Skip (Just sa', sb, Nothing, b)+ Stop -> Skip (Nothing, sb, Nothing, b)++ step gst (sa, Just sb, a, Nothing) = do+ r <- stepb gst sb+ return $ case r of+ Yield b sb' -> Skip (sa, Just sb', a, Just b)+ Skip sb' -> Skip (sa, Just sb', a, Nothing)+ Stop -> Skip (sa, Nothing, a, Nothing)++ -- both the values are available+ step _ (sa, sb, Just a, Just b) = do+ res <- cmp a b+ return $ case res of+ GT -> Yield b (sa, sb, Just a, Nothing)+ _ -> Yield a (sa, sb, Nothing, Just b)++ -- one of the values is missing, corresponding stream is done+ step _ (Nothing, sb, Nothing, Just b) =+ return $ Yield b (Nothing, sb, Nothing, Nothing)++ step _ (sa, Nothing, Just a, Nothing) =+ return $ Yield a (sa, Nothing, Nothing, Nothing)++ step _ (Nothing, Nothing, Nothing, Nothing) = return Stop++{-# INLINE mergeBy #-}+mergeBy+ :: (Monad m)+ => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+mergeBy cmp = mergeByM (\a b -> return $ cmp a b)++------------------------------------------------------------------------------+-- Transformation comprehensions+------------------------------------------------------------------------------++{-# INLINE_NORMAL the #-}+the :: (Eq a, Monad m) => Stream m a -> m (Maybe a)+the (Stream step state) = go state+ where+ go st = do+ r <- step defState st+ case r of+ Yield x s -> go' x s+ Skip s -> go s+ Stop -> return Nothing+ go' n st = do+ r <- step defState st+ case r of+ Yield x s | x == n -> go' n s+ | otherwise -> return Nothing+ Skip s -> go' n s+ Stop -> return (Just n)++{-# INLINE runFold #-}+runFold :: (Monad m) => Fold m a b -> Stream m a -> m b+runFold (Fold step begin done) = foldlMx' step begin done++-------------------------------------------------------------------------------+-- Concurrent application and fold+-------------------------------------------------------------------------------++-- XXX These functions should be moved to Stream/Parallel.hs+--+-- Using StreamD the worker stream producing code can fuse with the code to+-- queue output to the SVar giving some perf boost.+--+-- Note that StreamD can only be used in limited situations, specifically, we+-- cannot implement joinStreamVarPar using this.+--+-- XXX make sure that the SVar passed is a Parallel style SVar.++-- | Fold the supplied stream to the SVar asynchronously using Parallel+-- concurrency style.+-- {-# INLINE_NORMAL toSVarParallel #-}+{-# INLINE toSVarParallel #-}+toSVarParallel :: MonadAsync m+ => State t m a -> SVar t m a -> Stream m a -> m ()+toSVarParallel st sv xs =+ if svarInspectMode sv+ then forkWithDiag+ else do+ tid <-+ case getYieldLimit st of+ Nothing -> doFork (work Nothing)+ (svarMrun sv)+ (handleChildException sv)+ Just _ -> doFork (workLim Nothing)+ (svarMrun sv)+ (handleChildException sv)+ modifyThread sv tid++ where++ {-# NOINLINE work #-}+ work info = (runFold (FL.toParallelSVar sv info) xs)++ {-# NOINLINE workLim #-}+ workLim info = runFold (FL.toParallelSVarLimited sv info) xs++ {-# NOINLINE forkWithDiag #-}+ forkWithDiag = do+ -- We do not use workerCount in case of ParallelVar but still there is+ -- no harm in maintaining it correctly.+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1+ recordMaxWorkers sv+ -- This allocation matters when significant number of workers are being+ -- sent. We allocate it only when needed. The overhead increases by 4x.+ winfo <-+ case yieldRateInfo sv of+ Nothing -> return Nothing+ Just _ -> liftIO $ do+ cntRef <- newIORef 0+ t <- getTime Monotonic+ lat <- newIORef (0, t)+ return $ Just WorkerInfo+ { workerYieldMax = 0+ , workerYieldCount = cntRef+ , workerLatencyStart = lat+ }+ tid <-+ case getYieldLimit st of+ Nothing -> doFork (work winfo)+ (svarMrun sv)+ (handleChildException sv)+ Just _ -> doFork (workLim winfo)+ (svarMrun sv)+ (handleChildException sv)+ modifyThread sv tid++{-# INLINE_NORMAL mkParallelD #-}+mkParallelD :: MonadAsync m => Stream m a -> Stream m a+mkParallelD m = Stream step Nothing+ where++ step gst Nothing = do+ sv <- newParallelVar StopNone gst+ toSVarParallel gst sv m+ -- XXX use unfold instead?+ return $ Skip $ Just $ fromSVar sv++ step gst (Just (UnStream step1 st)) = do+ r <- step1 gst st+ return $ case r of+ Yield a s -> Yield a (Just $ Stream step1 s)+ Skip s -> Skip (Just $ Stream step1 s)+ Stop -> Stop++-- Compare with mkAsync. mkAsync uses an Async style SVar whereas this uses a+-- parallel style SVar for evaluation. Currently, parallel style cannot use+-- rate control whereas Async style can use rate control. In async style SVar+-- the worker thread terminates when the buffer is full whereas in Parallel+-- style it blocks.+--+-- | Make the stream producer and consumer run concurrently by introducing a+-- buffer between them. The producer thread evaluates the input stream until+-- the buffer fills, it blocks if the buffer is full until there is space in+-- the buffer. The consumer consumes the stream lazily from the buffer.+--+-- /Internal/+--+{-# INLINE_NORMAL mkParallel #-}+mkParallel :: (K.IsStream t, MonadAsync m) => t m a -> t m a+mkParallel = fromStreamD . mkParallelD . toStreamD++-------------------------------------------------------------------------------+-- Concurrent tap+-------------------------------------------------------------------------------++-- | Create an SVar with a fold consumer that will fold any elements sent to it+-- using the supplied fold function.+{-# INLINE newFoldSVar #-}+newFoldSVar :: MonadAsync m => State t m a -> Fold m a b -> m (SVar t m a)+newFoldSVar stt f = do+ -- Buffer size for the SVar is derived from the current state+ sv <- newParallelVar StopAny (adaptState stt)+ -- Add the producer thread-id to the SVar.+ liftIO myThreadId >>= modifyThread sv+ void $ doFork (work sv) (svarMrun sv) (handleFoldException sv)+ return sv++ where++ {-# NOINLINE work #-}+ work sv = void $ runFold f $ fromProducer sv++data TapState sv st = TapInit | Tapping sv st | TapDone st++{-# INLINE_NORMAL tapAsync #-}+tapAsync :: MonadAsync m => Fold m a b -> Stream m a -> Stream m a+tapAsync f (Stream step1 state1) = Stream step TapInit+ where++ drainFold svr = do+ -- In general, a Stop event would come equipped with the result+ -- of the fold. It is not used here but it would be useful in+ -- applicative and distribute.+ done <- fromConsumer svr+ when (not done) $ do+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"+ $ takeMVar (outputDoorBellFromConsumer svr)+ drainFold svr++ stopFold svr = do+ liftIO $ sendStop svr Nothing+ -- drain/wait until a stop event arrives from the fold.+ drainFold svr++ {-# INLINE_LATE step #-}+ step gst TapInit = do+ sv <- newFoldSVar gst f+ return $ Skip (Tapping sv state1)++ step gst (Tapping sv st) = do+ r <- step1 gst st+ case r of+ Yield a s -> do+ done <- pushToFold sv a+ if done+ then do+ -- XXX we do not need to wait synchronously here+ stopFold sv+ return $ Yield a (TapDone s)+ else return $ Yield a (Tapping sv s)+ Skip s -> return $ Skip (Tapping sv s)+ Stop -> do+ stopFold sv+ return $ Stop++ step gst (TapDone st) = do+ r <- step1 gst st+ return $ case r of+ Yield a s -> Yield a (TapDone s)+ Skip s -> Skip (TapDone s)+ Stop -> Stop++-- XXX Exported from Array again as this fold is specific to Array+-- | Take last 'n' elements from the stream and discard the rest.+{-# INLINE lastN #-}+lastN :: (Storable a, MonadIO m) => Int -> Fold m a (Array a)+lastN n = Fold step initial done+ where+ step (Tuple3' rb rh i) a = do+ rh1 <- liftIO $ RB.unsafeInsert rb rh a+ return $ Tuple3' rb rh1 (i + 1)+ initial = fmap (\(a, b) -> Tuple3' a b (0 :: Int)) $ liftIO $ RB.new n+ done (Tuple3' rb rh i) = do+ arr <- liftIO $ A.newArray n+ foldFunc i rh snoc' arr rb+ snoc' b a = liftIO $ A.unsafeSnoc b a+ foldFunc i+ | i < n = RB.unsafeFoldRingM+ | otherwise = RB.unsafeFoldRingFullM++------------------------------------------------------------------------------+-- Time related+------------------------------------------------------------------------------++-- XXX using getTime in the loop can be pretty expensive especially for+-- computations where iterations are lightweight. We have the following+-- options:+--+-- 1) Run a timeout thread updating a flag asynchronously and check that+-- flag here, that way we can have a cheap termination check.+--+-- 2) Use COARSE clock to get time with lower resolution but more efficiently.+--+-- 3) Use rdtscp/rdtsc to get time directly from the processor, compute the+-- termination value of rdtsc in the beginning and then in each iteration just+-- get rdtsc and check if we should terminate.+--+data TakeByTime st s+ = TakeByTimeInit st+ | TakeByTimeCheck st s+ | TakeByTimeYield st s++{-# INLINE_NORMAL takeByTime #-}+takeByTime :: (MonadIO m, TimeUnit64 t) => t -> Stream m a -> Stream m a+takeByTime duration (Stream step1 state1) = Stream step (TakeByTimeInit state1)+ where++ lim = toRelTime64 duration++ {-# INLINE_LATE step #-}+ step _ (TakeByTimeInit _) | lim == 0 = return Stop+ step _ (TakeByTimeInit st) = do+ t0 <- liftIO $ getTime Monotonic+ return $ Skip (TakeByTimeYield st t0)+ step _ (TakeByTimeCheck st t0) = do+ t <- liftIO $ getTime Monotonic+ return $+ if diffAbsTime64 t t0 > lim+ then Stop+ else Skip (TakeByTimeYield st t0)+ step gst (TakeByTimeYield st t0) = do+ r <- step1 gst st+ return $ case r of+ Yield x s -> Yield x (TakeByTimeCheck s t0)+ Skip s -> Skip (TakeByTimeCheck s t0)+ Stop -> Stop++data DropByTime st s x+ = DropByTimeInit st+ | DropByTimeGen st s+ | DropByTimeCheck st s x+ | DropByTimeYield st++{-# INLINE_NORMAL dropByTime #-}+dropByTime :: (MonadIO m, TimeUnit64 t) => t -> Stream m a -> Stream m a+dropByTime duration (Stream step1 state1) = Stream step (DropByTimeInit state1)+ where++ lim = toRelTime64 duration++ {-# INLINE_LATE step #-}+ step _ (DropByTimeInit st) = do+ t0 <- liftIO $ getTime Monotonic+ return $ Skip (DropByTimeGen st t0)+ step gst (DropByTimeGen st t0) = do+ r <- step1 gst st+ return $ case r of+ Yield x s -> Skip (DropByTimeCheck s t0 x)+ Skip s -> Skip (DropByTimeGen s t0)+ Stop -> Stop+ step _ (DropByTimeCheck st t0 x) = do+ t <- liftIO $ getTime Monotonic+ if diffAbsTime64 t t0 <= lim+ then return $ Skip $ DropByTimeGen st t0+ else return $ Yield x $ DropByTimeYield st+ step gst (DropByTimeYield st) = do+ r <- step1 gst st+ return $ case r of+ Yield x s -> Yield x (DropByTimeYield s)+ Skip s -> Skip (DropByTimeYield s)+ Stop -> Stop++-- XXX we should move this to stream generation section of this file. Also, the+-- take/drop combinators above should be moved to filtering section.+{-# INLINE_NORMAL currentTime #-}+currentTime :: MonadAsync m => Double -> Stream m AbsTime+currentTime g = Stream step Nothing++ where++ g' = g * 10 ^ (6 :: Int)++ -- XXX should have a minimum granularity to avoid high CPU usage?+ {-# INLINE delayTime #-}+ delayTime =+ if g' >= fromIntegral (maxBound :: Int)+ then maxBound+ else round g'++ updateTimeVar timeVar = do+ threadDelay $ delayTime+ MicroSecond64 t <- fromAbsTime <$> getTime Monotonic+ modifyVar' timeVar (const t)++ {-# INLINE_LATE step #-}+ step _ Nothing = do+ -- XXX note that this is safe only on a 64-bit machine. On a 32-bit+ -- machine a 64-bit 'Var' cannot be read consistently without a lock+ -- while another thread is writing to it.+ timeVar <- liftIO $ newVar (0 :: Int64)+ tid <- forkManaged $ liftIO $ forever (updateTimeVar timeVar)+ return $ Skip $ Just (timeVar, tid)++ step _ s@(Just (timeVar, _)) = do+ a <- liftIO $ readVar timeVar+ -- XXX we can perhaps use an AbsTime64 using a 64 bit Int for+ -- efficiency. or maybe we can use a representation using Double for+ -- floating precision time+ return $ Yield (toAbsTime (MicroSecond64 a)) s
src/Streamly/Internal/Data/Stream/StreamD/Type.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-}@@ -15,7 +14,7 @@ -- | -- Module : Streamly.Internal.Data.Stream.StreamD.Type -- Copyright : (c) 2018 Harendra Kumar--- Copyright : (c) Roman Leshchinskiy 2008-2010+-- (c) Roman Leshchinskiy 2008-2010 -- -- License : BSD3 -- Maintainer : streamly@composewell.com@@ -68,18 +67,19 @@ where import Control.Applicative (liftA2)-import Control.Monad (ap, when)+import Control.Monad (when) import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.Trans (lift, MonadTrans) import Data.Functor.Identity (Identity(..)) import GHC.Base (build) import GHC.Types (SPEC(..)) import Prelude hiding (map, mapM, foldr, take, concatMap)+import Fusion.Plugin.Types (Fuse(..)) import Streamly.Internal.Data.SVar (State(..), adaptState, defState) import Streamly.Internal.Data.Fold.Types (Fold(..), Fold2(..)) -import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamK as K ------------------------------------------------------------------------------ -- The direct style stream type@@ -88,15 +88,14 @@ -- | A stream is a succession of 'Step's. A 'Yield' produces a single value and -- the next state of the stream. 'Stop' indicates there are no more values in -- the stream.+{-# ANN type Step Fuse #-} data Step s a = Yield a s | Skip s | Stop -{- instance Functor (Step s) where {-# INLINE fmap #-} fmap f (Yield x s) = Yield (f x) s fmap _ (Skip s) = Skip s fmap _ Stop = Stop--} -- gst = global state -- | A stream consists of a step function that generates the next step given a@@ -131,12 +130,14 @@ toStreamK :: Monad m => Stream m a -> K.Stream m a toStreamK (Stream step state) = go state where- go st = K.mkStream $ \gst yld sng stp -> do- r <- step gst st- case r of- Yield x s -> yld x (go s)- Skip s -> K.foldStreamShared gst yld sng stp $ go s- Stop -> stp+ go st = K.mkStream $ \gst yld _ stp ->+ let go' ss = do+ r <- step gst ss+ case r of+ Yield x s -> yld x (go s)+ Skip s -> go' s+ Stop -> stp+ in go' st #ifndef DISABLE_FUSION {-# RULES "fromStreamK/toStreamK fusion"@@ -174,9 +175,12 @@ map :: Monad m => (a -> b) -> Stream m a -> Stream m b map f = mapM (return . f) -instance Monad m => Functor (Stream m) where+instance Functor m => Functor (Stream m) where {-# INLINE fmap #-}- fmap = map+ fmap f (Stream step state) = Stream step' state+ where+ {-# INLINE_LATE step' #-}+ step' gst st = fmap (fmap f) (step (adaptState gst) st) ------------------------------------------------------------------------------ -- concatMap@@ -229,19 +233,62 @@ -- | Create a singleton 'Stream' from a pure value. {-# INLINE_NORMAL yield #-}-yield :: Monad m => a -> Stream m a-yield x = Stream (\_ s -> return $ step undefined s) True+yield :: Applicative m => a -> Stream m a+yield x = Stream (\_ s -> pure $ step undefined s) True where {-# INLINE_LATE step #-} step _ True = Yield x False step _ False = Stop -instance Monad m => Applicative (Stream m) where+{-# INLINE_NORMAL concatAp #-}+concatAp :: Functor f => Stream f (a -> b) -> Stream f a -> Stream f b+concatAp (Stream stepa statea) (Stream stepb stateb) = Stream step' (Left statea)+ where+ {-# INLINE_LATE step' #-}+ step' gst (Left st) = fmap+ (\r -> case r of+ Yield f s -> Skip (Right (f, s, stateb))+ Skip s -> Skip (Left s)+ Stop -> Stop)+ (stepa (adaptState gst) st)+ step' gst (Right (f, os, st)) = fmap+ (\r -> case r of+ Yield a s -> Yield (f a) (Right (f, os, s))+ Skip s -> Skip (Right (f,os, s))+ Stop -> Skip (Left os))+ (stepb (adaptState gst) st)++{-# INLINE_NORMAL apSequence #-}+apSequence :: Functor f => Stream f a -> Stream f b -> Stream f b+apSequence (Stream stepa statea) (Stream stepb stateb) = Stream step (Left statea)+ where+ {-# INLINE_LATE step #-}+ step gst (Left st) =+ fmap+ (\r ->+ case r of+ Yield _ s -> Skip (Right (s, stateb))+ Skip s -> Skip (Left s)+ Stop -> Stop)+ (stepa (adaptState gst) st)+ step gst (Right (ostate, st)) =+ fmap+ (\r ->+ case r of+ Yield b s -> Yield b (Right (ostate, s))+ Skip s -> Skip (Right (ostate, s))+ Stop -> Skip (Left ostate))+ (stepb gst st)++instance Applicative f => Applicative (Stream f) where {-# INLINE pure #-} pure = yield {-# INLINE (<*>) #-}- (<*>) = ap+ (<*>) = concatAp+ {-# INLINE (*>) #-}+ (*>) = apSequence + -- NOTE: even though concatMap for StreamD is 4x faster compared to StreamK, -- the monad instance does not seem to be significantly faster. instance Monad m => Monad (Stream m) where@@ -249,6 +296,8 @@ return = pure {-# INLINE (>>=) #-} (>>=) = flip concatMap+ {-# INLINE (>>) #-}+ (>>) = (*>) instance MonadTrans Stream where lift = yieldM@@ -418,12 +467,12 @@ -- | Convert a list of pure values to a 'Stream' {-# INLINE_LATE fromList #-}-fromList :: Monad m => [a] -> Stream m a+fromList :: Applicative m => [a] -> Stream m a fromList = Stream step where {-# INLINE_LATE step #-}- step _ (x:xs) = return $ Yield x xs- step _ [] = return Stop+ step _ (x:xs) = pure $ Yield x xs+ step _ [] = pure Stop ------------------------------------------------------------------------------ -- Comparisons@@ -538,7 +587,7 @@ r <- step (adaptState gst) st case r of Yield x s -> do- fs' <- fstep fs x+ !fs' <- fstep fs x let i' = i + 1 return $ if i' >= n@@ -582,7 +631,7 @@ r <- step (adaptState gst) st case r of Yield x s -> do- fs' <- fstep fs x+ !fs' <- fstep fs x let i' = i + 1 return $ if i' >= n
+ src/Streamly/Internal/Data/Stream/StreamDK.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+-- {-# LANGUAGE ScopedTypeVariables #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.StreamDK+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++module Streamly.Internal.Data.Stream.StreamDK+ (+ -- * Stream Type++ Stream+ , Step (..)++ -- * Construction+ , nil+ , cons+ , consM+ , unfoldr+ , unfoldrM+ , replicateM++ -- * Folding+ , uncons+ , foldrS++ -- * Specific Folds+ , drain+ )+where++import Streamly.Internal.Data.Stream.StreamDK.Type (Stream(..), Step(..))++-------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------++nil :: Monad m => Stream m a+nil = Stream $ return Stop++{-# INLINE_NORMAL cons #-}+cons :: Monad m => a -> Stream m a -> Stream m a+cons x xs = Stream $ return $ Yield x xs++consM :: Monad m => m a -> Stream m a -> Stream m a+consM eff xs = Stream $ eff >>= \x -> return $ Yield x xs++unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a+unfoldrM next state = Stream (step' state)+ where+ step' st = do+ r <- next st+ return $ case r of+ Just (x, s) -> Yield x (Stream (step' s))+ Nothing -> Stop+{-+unfoldrM next s0 = buildM $ \yld stp ->+ let go s = do+ r <- next s+ case r of+ Just (a, b) -> yld a (go b)+ Nothing -> stp+ in go s0+-}++{-# INLINE unfoldr #-}+unfoldr :: Monad m => (b -> Maybe (a, b)) -> b -> Stream m a+unfoldr next s0 = build $ \yld stp ->+ let go s =+ case next s of+ Just (a, b) -> yld a (go b)+ Nothing -> stp+ in go s0++replicateM :: Monad m => Int -> a -> Stream m a+replicateM n x = Stream (step n)+ where+ step i = return $+ if i <= 0+ then Stop+ else Yield x (Stream (step (i - 1)))++-------------------------------------------------------------------------------+-- Folding+-------------------------------------------------------------------------------++uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))+uncons (Stream step) = do+ r <- step+ return $ case r of+ Yield x xs -> Just (x, xs)+ Stop -> Nothing++-- | Lazy right associative fold to a stream.+{-# INLINE_NORMAL foldrS #-}+foldrS :: Monad m+ => (a -> Stream m b -> Stream m b)+ -> Stream m b+ -> Stream m a+ -> Stream m b+foldrS f streamb = go+ where+ go (Stream stepa) = Stream $ do+ r <- stepa+ case r of+ Yield x xs -> let Stream step = f x (go xs) in step+ Stop -> let Stream step = streamb in step++{-# INLINE_LATE foldrM #-}+foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b+foldrM fstep acc ys = go ys+ where+ go (Stream step) = do+ r <- step+ case r of+ Yield x xs -> fstep x (go xs)+ Stop -> acc++{-# INLINE_NORMAL build #-}+build :: Monad m+ => forall a. (forall b. (a -> b -> b) -> b -> b) -> Stream m a+build g = g cons nil++{-# RULES+"foldrM/build" forall k z (g :: forall b. (a -> b -> b) -> b -> b).+ foldrM k z (build g) = g k z #-}++{-+-- To fuse foldrM with unfoldrM we need the type m1 to be polymorphic such that+-- it is either Monad m or Stream m. So that we can use cons/nil as well as+-- monadic construction function as its arguments.+--+{-# INLINE_NORMAL buildM #-}+buildM :: Monad m+ => forall a. (forall b. (a -> m1 b -> m1 b) -> m1 b -> m1 b) -> Stream m a+buildM g = g cons nil+-}++-------------------------------------------------------------------------------+-- Specific folds+-------------------------------------------------------------------------------++{-# INLINE drain #-}+drain :: Monad m => Stream m a -> m ()+drain = foldrM (\_ xs -> xs) (return ())+{-+drain (Stream step) = do+ r <- step+ case r of+ Yield _ next -> drain next+ Stop -> return ()+ -}
+ src/Streamly/Internal/Data/Stream/StreamDK/Type.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}++-- |+-- Module : Streamly.StreamDK.Type+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- A CPS style stream using a constructor based representation instead of a+-- function based representation.+--+-- Streamly internally uses two fundamental stream representations, (1) streams+-- with an open or arbitrary control flow (we call it StreamK), (2) streams+-- with a structured or closed loop control flow (we call it StreamD). The+-- higher level stream types can use any of these representations under the+-- hood and can interconvert between the two.+--+-- StreamD:+--+-- StreamD is a non-recursive data type in which the state of the stream and+-- the step function are separate. When the step function is called, a stream+-- element and the new stream state is yielded. The generated element and the+-- state are passed to the next consumer in the loop. The state is threaded+-- around in the loop until control returns back to the original step function+-- to run the next step. This creates a structured closed loop representation+-- (like "for" loops in C) with state of each step being hidden/abstracted or+-- existential within that step. This creates a loop representation identical+-- to the "for" or "while" loop constructs in imperative languages, the states+-- of the steps combined together constitute the state of the loop iteration.+--+-- Internally most combinators use a closed loop representation because it+-- provides very high efficiency due to stream fusion. The performance of this+-- representation is competitive to the C language implementations.+--+-- Pros and Cons of StreamD:+--+-- 1) stream-fusion: This representation can be optimized very efficiently by+-- the compiler because the state is explicitly separated from step functions,+-- represented using pure data constructors and visible to the compiler, the+-- stream steps can be fused using case-of-case transformations and the state+-- can be specialized using spec-constructor optimization, yielding a C like+-- tight loop/state machine with no constructors, the state is used unboxed and+-- therefore no unnecessary allocation.+--+-- 2) Because of a closed representation consing too many elements in this type+-- of stream does not scale, it will have quadratic performance slowdown. Each+-- cons creates a layer that needs to return the control back to the caller.+-- Another implementation of cons is possible but that will have to box/unbox+-- the state and will not fuse. So effectively cons breaks fusion.+--+-- 3) unconsing an item from the stream breaks fusion, we have to "pause" the+-- loop, rebox and save the state.+--+-- 3) Exception handling is easy to implement in this model because control+-- flow is structured in the loop and cannot be arbitrary. Therefore,+-- implementing "bracket" is natural.+--+-- 4) Round-robin scheduling for co-operative multitasking is easy to implement.+--+-- 5) It fuses well with the direct style Fold implementation.+--+-- StreamK/StreamDK:+--+-- StreamDK i.e. the stream defined in this module, like StreamK, is a+-- recursive data type which has no explicit state defined using constructors,+-- each step yields an element and a computation representing the rest of the+-- stream. Stream state is part of the function representing the rest of the+-- stream. This creates an open computation representation, or essentially a+-- continuation passing style computation. After the stream step is executed,+-- the caller is free to consume the produced element and then send the control+-- wherever it wants, there is no restriction on the control to return back+-- somewhere, the control is free to go anywhere. The caller may decide not to+-- consume the rest of the stream. This representation is more like a "goto"+-- based implementation in imperative languages.+--+-- Pros and Cons of StreamK:+--+-- 1) The way StreamD can be optimized using stream-fusion, this type can be+-- optimized using foldr/build fusion. However, foldr/build has not yet been+-- fully implemented for StreamK/StreamDK.+--+-- 2) Using cons is natural in this representation, unlike in StreamD it does+-- not have a quadratic slowdown. Currently, we in fact wrap StreamD in StreamK+-- to support a better cons operation.+--+-- 3) Similarly, uncons is natural in this representation.+--+-- 4) Exception handling is not easy to implement because of the "goto" nature+-- of CPS.+--+-- 5) Composable folds are not implemented/proven, however, intuition says that+-- a push style CPS representation should be able to be used along with StreamK+-- to efficiently implement composable folds.++module Streamly.Internal.Data.Stream.StreamDK.Type+ ( Step(..)+ , Stream (..)+ )+where++-- XXX Use Cons and Nil instead of Yield and Stop?+data Step m a = Yield a (Stream m a) | Stop++data Stream m a = Stream (m (Step m a))
+ src/Streamly/Internal/Data/Stream/StreamK.hs view
@@ -0,0 +1,1095 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.StreamK+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+-- Continuation passing style (CPS) stream implementation. The symbol 'K' below+-- denotes a function as well as a Kontinuation.+--+-- @+-- import qualified Streamly.Internal.Data.Stream.StreamK as K+-- @+--+module Streamly.Internal.Data.Stream.StreamK+ (+ -- * A class for streams+ IsStream (..)+ , adapt++ -- * The stream type+ , Stream(..)++ -- * Construction Primitives+ , mkStream+ , nil+ , nilM+ , cons+ , (.:)++ -- * Elimination Primitives+ , foldStream+ , foldStreamShared++ -- * Transformation Primitives+ , unShare++ -- * Deconstruction+ , uncons++ -- * Generation+ -- ** Unfolds+ , unfoldr+ , unfoldrM++ -- ** Specialized Generation+ , repeat+ , repeatM+ , replicate+ , replicateM+ , fromIndices+ , fromIndicesM+ , iterate+ , iterateM++ -- ** Conversions+ , yield+ , yieldM+ , fromFoldable+ , fromList+ , fromStreamK++ -- * foldr/build+ , foldrS+ , foldrSM+ , buildS+ , buildM+ , augmentS+ , augmentSM++ -- * Elimination+ -- ** General Folds+ , foldr+ , foldr1+ , foldrM+ , foldrT++ , foldl'+ , foldlM'+ , foldlS+ , foldlT+ , foldlx'+ , foldlMx'++ -- ** Specialized Folds+ , drain+ , null+ , head+ , tail+ , init+ , elem+ , notElem+ , all+ , any+ , last+ , minimum+ , minimumBy+ , maximum+ , maximumBy+ , findIndices+ , lookup+ , findM+ , find+ , (!!)++ -- ** Map and Fold+ , mapM_++ -- ** Conversions+ , toList+ , toStreamK+ , hoist++ -- * Transformation+ -- ** By folding (scans)+ , scanl'+ , scanlx'++ -- ** Filtering+ , filter+ , take+ , takeWhile+ , drop+ , dropWhile++ -- ** Mapping+ , map+ , mapM+ , mapMSerial+ , sequence++ -- ** Inserting+ , intersperseM+ , intersperse+ , insertBy++ -- ** Deleting+ , deleteBy++ -- ** Reordering+ , reverse++ -- ** Map and Filter+ , mapMaybe++ -- ** Zipping+ , zipWith+ , zipWithM++ -- ** Merging+ , mergeBy+ , mergeByM++ -- ** Nesting+ , concatMapBy+ , concatMap+ , bindWith++ -- ** Transformation comprehensions+ , the++ -- * Semigroup Style Composition+ , serial++ -- * Utilities+ , consMStream+ , withLocal+ , mfix++ -- * Deprecated+ , Streaming -- deprecated+ , once -- deprecated+ )+where++import Control.Monad.Trans (MonadTrans(lift))+import Control.Monad (void, join)+import Control.Monad.Reader.Class (MonadReader(..))+import Data.Function (fix)+import Prelude+ hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,+ take, filter, all, any, takeWhile, drop, dropWhile, minimum,+ maximum, elem, notElem, null, head, tail, init, zipWith, lookup,+ foldr1, (!!), replicate, reverse, concatMap, iterate)+import qualified Prelude++import Streamly.Internal.Data.SVar+import Streamly.Internal.Data.Stream.StreamK.Type++-------------------------------------------------------------------------------+-- Deconstruction+-------------------------------------------------------------------------------++{-# INLINE uncons #-}+uncons :: (IsStream t, Monad m) => t m a -> m (Maybe (a, t m a))+uncons m =+ let stop = return Nothing+ single a = return (Just (a, nil))+ yieldk a r = return (Just (a, r))+ in foldStream defState yieldk single stop m++-------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++{-# INLINE unfoldr #-}+unfoldr :: IsStream t => (b -> Maybe (a, b)) -> b -> t m a+unfoldr next s0 = build $ \yld stp ->+ let go s =+ case next s of+ Just (a, b) -> yld a (go b)+ Nothing -> stp+ in go s0++{-# INLINE unfoldrM #-}+unfoldrM :: (IsStream t, MonadAsync m) => (b -> m (Maybe (a, b))) -> b -> t m a+unfoldrM step = go+ where+ go s = sharedM $ \yld _ stp -> do+ r <- step s+ case r of+ Just (a, b) -> yld a (go b)+ Nothing -> stp++{-+-- Generalization of concurrent streams/SVar via unfoldr.+--+-- Unfold a value into monadic actions and then run the resulting monadic+-- actions to generate a stream. Since the step of generating the monadic+-- action and running them are decoupled we can run the monadic actions+-- cooncurrently. For example, the seed could be a list of monadic actions or a+-- pure stream of monadic actions.+--+-- We can have different flavors of this depending on the stream type t. The+-- concurrent version could be async or ahead etc. Depending on how we queue+-- back the feedback portion b, it could be DFS or BFS style.+--+unfoldrA :: (IsStream t, MonadAsync m) => (b -> Maybe (m a, b)) -> b -> t m a+unfoldrA = undefined+-}++-------------------------------------------------------------------------------+-- Special generation+-------------------------------------------------------------------------------++-- | Same as yieldM+--+-- @since 0.2.0+{-# DEPRECATED once "Please use yieldM instead." #-}+{-# INLINE once #-}+once :: (Monad m, IsStream t) => m a -> t m a+once = yieldM++-- |+-- @+-- repeatM = fix . cons+-- repeatM = cycle1 . yield+-- @+--+-- Generate an infinite stream by repeating a monadic value.+--+-- /Internal/+repeatM :: (IsStream t, MonadAsync m) => m a -> t m a+repeatM = go+ where go m = m |: go m++-- Generate an infinite stream by repeating a pure value.+--+-- /Internal/+{-# INLINE repeat #-}+repeat :: IsStream t => a -> t m a+repeat a = let x = cons a x in x++{-# INLINE replicateM #-}+replicateM :: (IsStream t, MonadAsync m) => Int -> m a -> t m a+replicateM n m = go n+ where+ go cnt = if cnt <= 0 then nil else m |: go (cnt - 1)++{-# INLINE replicate #-}+replicate :: IsStream t => Int -> a -> t m a+replicate n a = go n+ where+ go cnt = if cnt <= 0 then nil else a `cons` go (cnt - 1)++{-# INLINE fromIndicesM #-}+fromIndicesM :: (IsStream t, MonadAsync m) => (Int -> m a) -> t m a+fromIndicesM gen = go 0+ where+ go i = mkStream $ \st stp sng yld -> do+ foldStreamShared st stp sng yld (gen i |: go (i + 1))++{-# INLINE fromIndices #-}+fromIndices :: IsStream t => (Int -> a) -> t m a+fromIndices gen = go 0+ where+ go n = (gen n) `cons` go (n + 1)++{-# INLINE iterate #-}+iterate :: IsStream t => (a -> a) -> a -> t m a+iterate step = fromStream . go+ where+ go s = cons s (go (step s))++{-# INLINE iterateM #-}+iterateM :: (IsStream t, MonadAsync m) => (a -> m a) -> m a -> t m a+iterateM step = go+ where+ go s = mkStream $ \st stp sng yld -> do+ next <- s+ foldStreamShared st stp sng yld (return next |: go (step next))++-------------------------------------------------------------------------------+-- Conversions+-------------------------------------------------------------------------------++-- |+-- @+-- fromFoldable = 'Prelude.foldr' 'cons' 'nil'+-- @+--+-- Construct a stream from a 'Foldable' containing pure values:+--+-- @since 0.2.0+{-# INLINE fromFoldable #-}+fromFoldable :: (IsStream t, Foldable f) => f a -> t m a+fromFoldable = Prelude.foldr cons nil++{-# INLINE fromList #-}+fromList :: IsStream t => [a] -> t m a+fromList = fromFoldable++{-# INLINE fromStreamK #-}+fromStreamK :: IsStream t => Stream m a -> t m a+fromStreamK = fromStream++-------------------------------------------------------------------------------+-- Elimination by Folding+-------------------------------------------------------------------------------++-- | Lazy right associative fold.+{-# INLINE foldr #-}+foldr :: (IsStream t, Monad m) => (a -> b -> b) -> b -> t m a -> m b+foldr step acc = foldrM (\x xs -> xs >>= \b -> return (step x b)) (return acc)++-- | Right associative fold to an arbitrary transformer monad.+{-# INLINE foldrT #-}+foldrT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)+ => (a -> s m b -> s m b) -> s m b -> t m a -> s m b+foldrT step final m = go m+ where+ go m1 = do+ res <- lift $ uncons m1+ case res of+ Just (h, t) -> step h (go t)+ Nothing -> final++{-# INLINE foldr1 #-}+foldr1 :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> m (Maybe a)+foldr1 step m = do+ r <- uncons m+ case r of+ Nothing -> return Nothing+ Just (h, t) -> fmap Just (go h t)+ where+ go p m1 =+ let stp = return p+ single a = return $ step a p+ yieldk a r = fmap (step p) (go a r)+ in foldStream defState yieldk single stp m1++-- | Strict left fold with an extraction function. Like the standard strict+-- left fold, but applies a user supplied extraction function (the third+-- argument) to the folded value at the end. This is designed to work with the+-- @foldl@ library. The suffix @x@ is a mnemonic for extraction.+--+-- Note that the accumulator is always evaluated including the initial value.+{-# INLINE foldlx' #-}+foldlx' :: forall t m a b x. (IsStream t, Monad m)+ => (x -> a -> x) -> x -> (x -> b) -> t m a -> m b+foldlx' step begin done m = get $ go m begin+ where+ {-# NOINLINE get #-}+ get :: t m x -> m b+ get m1 =+ -- XXX we are not strictly evaluating the accumulator here. Is this+ -- okay?+ let single = return . done+ -- XXX this is foldSingleton. why foldStreamShared?+ in foldStreamShared undefined undefined single undefined m1++ -- Note, this can be implemented by making a recursive call to "go",+ -- however that is more expensive because of unnecessary recursion+ -- that cannot be tail call optimized. Unfolding recursion explicitly via+ -- continuations is much more efficient.+ go :: t m a -> x -> t m x+ go m1 !acc = mkStream $ \_ yld sng _ ->+ let stop = sng acc+ single a = sng $ step acc a+ -- XXX this is foldNonEmptyStream+ yieldk a r = foldStream defState yld sng undefined $+ go r (step acc a)+ in foldStream defState yieldk single stop m1++-- | Strict left associative fold.+{-# INLINE foldl' #-}+foldl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> m b+foldl' step begin = foldlx' step begin id++-- XXX replace the recursive "go" with explicit continuations.+-- | Like 'foldx', but with a monadic step function.+{-# INLINABLE foldlMx' #-}+foldlMx' :: (IsStream t, Monad m)+ => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> m b+foldlMx' step begin done m = go begin m+ where+ go !acc m1 =+ let stop = acc >>= done+ single a = acc >>= \b -> step b a >>= done+ yieldk a r = acc >>= \b -> step b a >>= \x -> go (return x) r+ in foldStream defState yieldk single stop m1++-- | Like 'foldl'' but with a monadic step function.+{-# INLINE foldlM' #-}+foldlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> m b+foldlM' step begin = foldlMx' step (return begin) return++-- | Lazy left fold to a stream.+{-# INLINE foldlS #-}+foldlS :: IsStream t => (t m b -> a -> t m b) -> t m b -> t m a -> t m b+foldlS step begin m = go begin m+ where+ go acc rest = mkStream $ \st yld sng stp ->+ let run x = foldStream st yld sng stp x+ stop = run acc+ single a = run $ step acc a+ yieldk a r = run $ go (step acc a) r+ in foldStream (adaptState st) yieldk single stop rest++-- | Lazy left fold to an arbitrary transformer monad.+{-# INLINE foldlT #-}+foldlT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)+ => (s m b -> a -> s m b) -> s m b -> t m a -> s m b+foldlT step begin m = go begin m+ where+ go acc m1 = do+ res <- lift $ uncons m1+ case res of+ Just (h, t) -> go (step acc h) t+ Nothing -> acc++------------------------------------------------------------------------------+-- Specialized folds+------------------------------------------------------------------------------++-- XXX use foldrM to implement folds where possible+-- XXX This (commented) definition of drain and mapM_ perform much better on+-- some benchmarks but worse on others. Need to investigate why, may there is+-- an optimization opportunity that we can exploit.+-- drain = foldrM (\_ xs -> return () >> xs) (return ())++-- |+-- > drain = foldl' (\_ _ -> ()) ()+-- > drain = mapM_ (\_ -> return ())+{-# INLINE drain #-}+drain :: (Monad m, IsStream t) => t m a -> m ()+drain = foldrM (\_ xs -> xs) (return ())+{-+drain = go+ where+ go m1 =+ let stop = return ()+ single _ = return ()+ yieldk _ r = go r+ in foldStream defState yieldk single stop m1+-}++{-# INLINE null #-}+null :: (IsStream t, Monad m) => t m a -> m Bool+-- null = foldrM (\_ _ -> return True) (return False)+null m =+ let stop = return True+ single _ = return False+ yieldk _ _ = return False+ in foldStream defState yieldk single stop m++{-# INLINE head #-}+head :: (IsStream t, Monad m) => t m a -> m (Maybe a)+-- head = foldrM (\x _ -> return $ Just x) (return Nothing)+head m =+ let stop = return Nothing+ single a = return (Just a)+ yieldk a _ = return (Just a)+ in foldStream defState yieldk single stop m++{-# INLINE tail #-}+tail :: (IsStream t, Monad m) => t m a -> m (Maybe (t m a))+tail m =+ let stop = return Nothing+ single _ = return $ Just nil+ yieldk _ r = return $ Just r+ in foldStream defState yieldk single stop m++{-# INLINE headPartial #-}+headPartial :: (IsStream t, Monad m) => t m a -> m a+headPartial = foldrM (\x _ -> return x) (error "head of nil")++{-# INLINE tailPartial #-}+tailPartial :: IsStream t => t m a -> t m a+tailPartial m = mkStream $ \st yld sng stp ->+ let stop = error "tail of nil"+ single _ = stp+ yieldk _ r = foldStream st yld sng stp r+ in foldStream st yieldk single stop m++-- | Iterate a lazy function `f` of the shape `m a -> t m a` until it gets+-- fully defined i.e. becomes independent of its argument action, then return+-- the resulting value of the function (`t m a`).+--+-- It can be used to construct a stream that uses a cyclic definition. For+-- example:+--+-- @+-- import Streamly.Internal.Prelude as S+-- import System.IO.Unsafe (unsafeInterleaveIO)+--+-- main = do+-- S.mapM_ print $ S.mfix $ \x -> do+-- a <- S.fromList [1,2]+-- b <- S.fromListM [return 3, unsafeInterleaveIO (fmap fst x)]+-- return (a, b)+-- @+--+-- Note that the function `f` must be lazy in its argument, that's why we use+-- 'unsafeInterleaveIO' because IO monad is strict.++mfix :: (IsStream t, Monad m) => (m a -> t m a) -> t m a+mfix f = mkStream $ \st yld sng stp ->+ let single a = foldStream st yld sng stp $ a `cons` ys+ yieldk a _ = foldStream st yld sng stp $ a `cons` ys+ in foldStream st yieldk single stp xs+ where xs = fix (f . headPartial)+ ys = mfix (tailPartial . f)++{-# INLINE init #-}+init :: (IsStream t, Monad m) => t m a -> m (Maybe (t m a))+init m = go1 m+ where+ go1 m1 = do+ r <- uncons m1+ case r of+ Nothing -> return Nothing+ Just (h, t) -> return . Just $ go h t+ go p m1 = mkStream $ \_ yld sng stp ->+ let single _ = sng p+ yieldk a x = yld p $ go a x+ in foldStream defState yieldk single stp m1++{-# INLINE elem #-}+elem :: (IsStream t, Monad m, Eq a) => a -> t m a -> m Bool+elem e m = go m+ where+ go m1 =+ let stop = return False+ single a = return (a == e)+ yieldk a r = if a == e then return True else go r+ in foldStream defState yieldk single stop m1++{-# INLINE notElem #-}+notElem :: (IsStream t, Monad m, Eq a) => a -> t m a -> m Bool+notElem e m = go m+ where+ go m1 =+ let stop = return True+ single a = return (a /= e)+ yieldk a r = if a == e then return False else go r+ in foldStream defState yieldk single stop m1++{-# INLINABLE all #-}+all :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m Bool+all p m = go m+ where+ go m1 =+ let single a | p a = return True+ | otherwise = return False+ yieldk a r | p a = go r+ | otherwise = return False+ in foldStream defState yieldk single (return True) m1++{-# INLINABLE any #-}+any :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m Bool+any p m = go m+ where+ go m1 =+ let single a | p a = return True+ | otherwise = return False+ yieldk a r | p a = return True+ | otherwise = go r+ in foldStream defState yieldk single (return False) m1++-- | Extract the last element of the stream, if any.+{-# INLINE last #-}+last :: (IsStream t, Monad m) => t m a -> m (Maybe a)+last = foldlx' (\_ y -> Just y) Nothing id++{-# INLINE minimum #-}+minimum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)+minimum m = go Nothing m+ where+ go Nothing m1 =+ let stop = return Nothing+ single a = return (Just a)+ yieldk a r = go (Just a) r+ in foldStream defState yieldk single stop m1++ go (Just res) m1 =+ let stop = return (Just res)+ single a =+ if res <= a+ then return (Just res)+ else return (Just a)+ yieldk a r =+ if res <= a+ then go (Just res) r+ else go (Just a) r+ in foldStream defState yieldk single stop m1++{-# INLINE minimumBy #-}+minimumBy+ :: (IsStream t, Monad m)+ => (a -> a -> Ordering) -> t m a -> m (Maybe a)+minimumBy cmp m = go Nothing m+ where+ go Nothing m1 =+ let stop = return Nothing+ single a = return (Just a)+ yieldk a r = go (Just a) r+ in foldStream defState yieldk single stop m1++ go (Just res) m1 =+ let stop = return (Just res)+ single a = case cmp res a of+ GT -> return (Just a)+ _ -> return (Just res)+ yieldk a r = case cmp res a of+ GT -> go (Just a) r+ _ -> go (Just res) r+ in foldStream defState yieldk single stop m1++{-# INLINE maximum #-}+maximum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)+maximum m = go Nothing m+ where+ go Nothing m1 =+ let stop = return Nothing+ single a = return (Just a)+ yieldk a r = go (Just a) r+ in foldStream defState yieldk single stop m1++ go (Just res) m1 =+ let stop = return (Just res)+ single a =+ if res <= a+ then return (Just a)+ else return (Just res)+ yieldk a r =+ if res <= a+ then go (Just a) r+ else go (Just res) r+ in foldStream defState yieldk single stop m1++{-# INLINE maximumBy #-}+maximumBy :: (IsStream t, Monad m) => (a -> a -> Ordering) -> t m a -> m (Maybe a)+maximumBy cmp m = go Nothing m+ where+ go Nothing m1 =+ let stop = return Nothing+ single a = return (Just a)+ yieldk a r = go (Just a) r+ in foldStream defState yieldk single stop m1++ go (Just res) m1 =+ let stop = return (Just res)+ single a = case cmp res a of+ GT -> return (Just res)+ _ -> return (Just a)+ yieldk a r = case cmp res a of+ GT -> go (Just res) r+ _ -> go (Just a) r+ in foldStream defState yieldk single stop m1++{-# INLINE (!!) #-}+(!!) :: (IsStream t, Monad m) => t m a -> Int -> m (Maybe a)+m !! i = go i m+ where+ go n m1 =+ let single a | n == 0 = return $ Just a+ | otherwise = return Nothing+ yieldk a x | n < 0 = return Nothing+ | n == 0 = return $ Just a+ | otherwise = go (n - 1) x+ in foldStream defState yieldk single (return Nothing) m1++{-# INLINE lookup #-}+lookup :: (IsStream t, Monad m, Eq a) => a -> t m (a, b) -> m (Maybe b)+lookup e m = go m+ where+ go m1 =+ let single (a, b) | a == e = return $ Just b+ | otherwise = return Nothing+ yieldk (a, b) x | a == e = return $ Just b+ | otherwise = go x+ in foldStream defState yieldk single (return Nothing) m1++{-# INLINE findM #-}+findM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> m (Maybe a)+findM p m = go m+ where+ go m1 =+ let single a = do+ b <- p a+ if b then return $ Just a else return Nothing+ yieldk a x = do+ b <- p a+ if b then return $ Just a else go x+ in foldStream defState yieldk single (return Nothing) m1++{-# INLINE find #-}+find :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m (Maybe a)+find p = findM (return . p)++{-# INLINE findIndices #-}+findIndices :: IsStream t => (a -> Bool) -> t m a -> t m Int+findIndices p = go 0+ where+ go offset m1 = mkStream $ \st yld sng stp ->+ let single a | p a = sng offset+ | otherwise = stp+ yieldk a x | p a = yld offset $ go (offset + 1) x+ | otherwise = foldStream (adaptState st) yld sng stp $+ go (offset + 1) x+ in foldStream (adaptState st) yieldk single stp m1++------------------------------------------------------------------------------+-- Map and Fold+------------------------------------------------------------------------------++-- | Apply a monadic action to each element of the stream and discard the+-- output of the action.+{-# INLINE mapM_ #-}+mapM_ :: (IsStream t, Monad m) => (a -> m b) -> t m a -> m ()+mapM_ f m = go m+ where+ go m1 =+ let stop = return ()+ single a = void (f a)+ yieldk a r = f a >> go r+ in foldStream defState yieldk single stop m1++------------------------------------------------------------------------------+-- Converting folds+------------------------------------------------------------------------------++{-# INLINABLE toList #-}+toList :: (IsStream t, Monad m) => t m a -> m [a]+toList = foldr (:) []++{-# INLINE toStreamK #-}+toStreamK :: Stream m a -> Stream m a+toStreamK = id++-- Based on suggestions by David Feuer and Pranay Sashank+{-# INLINE hoist #-}+hoist :: (IsStream t, Monad m, Monad n)+ => (forall x. m x -> n x) -> t m a -> t n a+hoist f str =+ mkStream $ \st yld sng stp ->+ let single = return . sng+ yieldk a s = return $ yld a (hoist f s)+ stop = return stp+ state = adaptState st+ in join . f $ foldStreamShared state yieldk single stop str++-------------------------------------------------------------------------------+-- Transformation by folding (Scans)+-------------------------------------------------------------------------------++{-# INLINE scanlx' #-}+scanlx' :: IsStream t => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b+scanlx' step begin done m =+ cons (done begin) $ go m begin+ where+ go m1 !acc = mkStream $ \st yld sng stp ->+ let single a = sng (done $ step acc a)+ yieldk a r =+ let s = step acc a+ in yld (done s) (go r s)+ in foldStream (adaptState st) yieldk single stp m1++{-# INLINE scanl' #-}+scanl' :: IsStream t => (b -> a -> b) -> b -> t m a -> t m b+scanl' step begin = scanlx' step begin id++-------------------------------------------------------------------------------+-- Filtering+-------------------------------------------------------------------------------++{-# INLINE filter #-}+filter :: IsStream t => (a -> Bool) -> t m a -> t m a+filter p m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a | p a = sng a+ | otherwise = stp+ yieldk a r | p a = yld a (go r)+ | otherwise = foldStream st yieldk single stp r+ in foldStream st yieldk single stp m1++{-# INLINE take #-}+take :: IsStream t => Int -> t m a -> t m a+take n m = go n m+ where+ go n1 m1 = mkStream $ \st yld sng stp ->+ let yieldk a r = yld a (go (n1 - 1) r)+ in if n1 <= 0+ then stp+ else foldStream st yieldk sng stp m1++{-# INLINE takeWhile #-}+takeWhile :: IsStream t => (a -> Bool) -> t m a -> t m a+takeWhile p m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a | p a = sng a+ | otherwise = stp+ yieldk a r | p a = yld a (go r)+ | otherwise = stp+ in foldStream st yieldk single stp m1++{-# INLINE drop #-}+drop :: IsStream t => Int -> t m a -> t m a+drop n m = fromStream $ unShare (go n (toStream m))+ where+ go n1 m1 = mkStream $ \st yld sng stp ->+ let single _ = stp+ yieldk _ r = foldStreamShared st yld sng stp $ go (n1 - 1) r+ -- Somehow "<=" check performs better than a ">"+ in if n1 <= 0+ then foldStreamShared st yld sng stp m1+ else foldStreamShared st yieldk single stp m1++{-# INLINE dropWhile #-}+dropWhile :: IsStream t => (a -> Bool) -> t m a -> t m a+dropWhile p m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a | p a = stp+ | otherwise = sng a+ yieldk a r | p a = foldStream st yieldk single stp r+ | otherwise = yld a r+ in foldStream st yieldk single stp m1++-------------------------------------------------------------------------------+-- Mapping+-------------------------------------------------------------------------------++-- Be careful when modifying this, this uses a consM (|:) deliberately to allow+-- other stream types to overload it.+{-# INLINE sequence #-}+sequence :: (IsStream t, MonadAsync m) => t m (m a) -> t m a+sequence m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single ma = ma >>= sng+ yieldk ma r = foldStreamShared st yld sng stp $ ma |: go r+ in foldStream (adaptState st) yieldk single stp m1++-------------------------------------------------------------------------------+-- Inserting+-------------------------------------------------------------------------------++{-# INLINE intersperseM #-}+intersperseM :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a+intersperseM a m = prependingStart m+ where+ prependingStart m1 = mkStream $ \st yld sng stp ->+ let yieldk i x = foldStreamShared st yld sng stp $ return i |: go x+ in foldStream st yieldk sng stp m1+ go m2 = mkStream $ \st yld sng stp ->+ let single i = foldStreamShared st yld sng stp $ a |: yield i+ yieldk i x = foldStreamShared st yld sng stp $ a |: return i |: go x+ in foldStream st yieldk single stp m2++{-# INLINE intersperse #-}+intersperse :: (IsStream t, MonadAsync m) => a -> t m a -> t m a+intersperse a = intersperseM (return a)++{-# INLINE insertBy #-}+insertBy :: IsStream t => (a -> a -> Ordering) -> a -> t m a -> t m a+insertBy cmp x m = go m+ where+ go m1 = mkStream $ \st yld _ _ ->+ let single a = case cmp x a of+ GT -> yld a (yield x)+ _ -> yld x (yield a)+ stop = yld x nil+ yieldk a r = case cmp x a of+ GT -> yld a (go r)+ _ -> yld x (a `cons` r)+ in foldStream st yieldk single stop m1++------------------------------------------------------------------------------+-- Deleting+------------------------------------------------------------------------------++{-# INLINE deleteBy #-}+deleteBy :: IsStream t => (a -> a -> Bool) -> a -> t m a -> t m a+deleteBy eq x m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a = if eq x a then stp else sng a+ yieldk a r = if eq x a+ then foldStream st yld sng stp r+ else yld a (go r)+ in foldStream st yieldk single stp m1++------------------------------------------------------------------------------+-- Reordering+------------------------------------------------------------------------------++{-# INLINE reverse #-}+reverse :: IsStream t => t m a -> t m a+reverse = foldlS (flip cons) nil++-------------------------------------------------------------------------------+-- Map and Filter+-------------------------------------------------------------------------------++{-# INLINE mapMaybe #-}+mapMaybe :: IsStream t => (a -> Maybe b) -> t m a -> t m b+mapMaybe f m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a = case f a of+ Just b -> sng b+ Nothing -> stp+ yieldk a r = case f a of+ Just b -> yld b $ go r+ Nothing -> foldStream (adaptState st) yieldk single stp r+ in foldStream (adaptState st) yieldk single stp m1++------------------------------------------------------------------------------+-- Serial Zipping+------------------------------------------------------------------------------++-- | Zip two streams serially using a pure zipping function.+--+-- @since 0.1.0+{-# INLINABLE zipWith #-}+zipWith :: IsStream t => (a -> b -> c) -> t m a -> t m b -> t m c+zipWith f = go+ where+ go mx my = mkStream $ \st yld sng stp -> do+ let merge a ra =+ let single2 b = sng (f a b)+ yield2 b rb = yld (f a b) (go ra rb)+ in foldStream (adaptState st) yield2 single2 stp my+ let single1 a = merge a nil+ yield1 = merge+ foldStream (adaptState st) yield1 single1 stp mx++-- | Zip two streams serially using a monadic zipping function.+--+-- @since 0.1.0+{-# INLINABLE zipWithM #-}+zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c+zipWithM f m1 m2 = go m1 m2+ where+ go mx my = mkStream $ \st yld sng stp -> do+ let merge a ra =+ let runIt x = foldStream st yld sng stp x+ single2 b = f a b >>= sng+ yield2 b rb = f a b >>= \x -> runIt (x `cons` go ra rb)+ in foldStream (adaptState st) yield2 single2 stp my+ let single1 a = merge a nil+ yield1 = merge+ foldStream (adaptState st) yield1 single1 stp mx++------------------------------------------------------------------------------+-- Merging+------------------------------------------------------------------------------++{-# INLINE mergeByM #-}+mergeByM+ :: (IsStream t, Monad m)+ => (a -> a -> m Ordering) -> t m a -> t m a -> t m a+mergeByM cmp = go+ where+ go mx my = mkStream $ \st yld sng stp -> do+ let mergeWithY a ra =+ let stop2 = foldStream st yld sng stp mx+ single2 b = do+ r <- cmp a b+ case r of+ GT -> yld b (go (a `cons` ra) nil)+ _ -> yld a (go ra (b `cons` nil))+ yield2 b rb = do+ r <- cmp a b+ case r of+ GT -> yld b (go (a `cons` ra) rb)+ _ -> yld a (go ra (b `cons` rb))+ in foldStream st yield2 single2 stop2 my+ let stopX = foldStream st yld sng stp my+ singleX a = mergeWithY a nil+ yieldX = mergeWithY+ foldStream st yieldX singleX stopX mx++{-# INLINABLE mergeBy #-}+mergeBy+ :: (IsStream t, Monad m)+ => (a -> a -> Ordering) -> t m a -> t m a -> t m a+mergeBy cmp = mergeByM (\a b -> return $ cmp a b)++------------------------------------------------------------------------------+-- Transformation comprehensions+------------------------------------------------------------------------------++{-# INLINE the #-}+the :: (Eq a, IsStream t, Monad m) => t m a -> m (Maybe a)+the m = do+ r <- uncons m+ case r of+ Nothing -> return Nothing+ Just (h, t) -> go h t+ where+ go h m1 =+ let single a | h == a = return $ Just h+ | otherwise = return Nothing+ yieldk a r | h == a = go h r+ | otherwise = return Nothing+ in foldStream defState yieldk single (return $ Just h) m1++------------------------------------------------------------------------------+-- Alternative & MonadPlus+------------------------------------------------------------------------------++_alt :: Stream m a -> Stream m a -> Stream m a+_alt m1 m2 = mkStream $ \st yld sng stp ->+ let stop = foldStream st yld sng stp m2+ in foldStream st yld sng stop m1++------------------------------------------------------------------------------+-- MonadReader+------------------------------------------------------------------------------++{-# INLINABLE withLocal #-}+withLocal :: MonadReader r m => (r -> r) -> Stream m a -> Stream m a+withLocal f m =+ mkStream $ \st yld sng stp ->+ let single = local f . sng+ yieldk a r = local f $ yld a (withLocal f r)+ in foldStream st yieldk single (local f stp) m++------------------------------------------------------------------------------+-- MonadError+------------------------------------------------------------------------------++{-+-- XXX handle and test cross thread state transfer+withCatchError+ :: MonadError e m+ => Stream m a -> (e -> Stream m a) -> Stream m a+withCatchError m h =+ mkStream $ \_ stp sng yld ->+ let run x = unStream x Nothing stp sng yieldk+ handle r = r `catchError` \e -> run $ h e+ yieldk a r = yld a (withCatchError r h)+ in handle $ run m+-}
+ src/Streamly/Internal/Data/Stream/StreamK/Type.hs view
@@ -0,0 +1,989 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ViewPatterns #-}+#if __GLASGOW_HASKELL__ >= 806+{-# LANGUAGE QuantifiedConstraints #-}+#endif+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Stream.StreamK.Type+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+-- Continuation passing style (CPS) stream implementation. The symbol 'K' below+-- denotes a function as well as a Kontinuation.+--+module Streamly.Internal.Data.Stream.StreamK.Type+ (+ -- * A class for streams+ IsStream (..)+ , adapt++ -- * The stream type+ , Stream (..)++ -- * Construction+ , mkStream+ , fromStopK+ , fromYieldK+ , consK++ -- * Elimination+ , foldStream+ , foldStreamShared++ -- * foldr/build+ , foldrM+ , foldrS+ , foldrSM+ , build+ , buildS+ , buildM+ , buildSM+ , sharedM+ , augmentS+ , augmentSM++ -- instances+ , cons+ , (.:)+ , consMStream+ , consMBy+ , yieldM+ , yield++ , nil+ , nilM+ , conjoin+ , serial+ , map+ , mapM+ , mapMSerial+ , unShare+ , concatMapBy+ , concatMap+ , bindWith++ , Streaming -- deprecated+ )+where++import Control.Monad (ap, (>=>))+import Control.Monad.Trans.Class (MonadTrans(lift))+#if __GLASGOW_HASKELL__ >= 800+import Data.Kind (Type)+#endif+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif+import Prelude hiding (map, mapM, concatMap, foldr)++import Streamly.Internal.Data.SVar++------------------------------------------------------------------------------+-- Basic stream type+------------------------------------------------------------------------------++-- | The type @Stream m a@ represents a monadic stream of values of type 'a'+-- constructed using actions in monad 'm'. It uses stop, singleton and yield+-- continuations equivalent to the following direct style type:+--+-- @+-- data Stream m a = Stop | Singleton a | Yield a (Stream m a)+-- @+--+-- To facilitate parallel composition we maintain a local state in an 'SVar'+-- that is shared across and is used for synchronization of the streams being+-- composed.+--+-- The singleton case can be expressed in terms of stop and yield but we have+-- it as a separate case to optimize composition operations for streams with+-- single element. We build singleton streams in the implementation of 'pure'+-- for Applicative and Monad, and in 'lift' for MonadTrans.+--+-- XXX remove the Stream type parameter from State as it is always constant.+-- We can remove it from SVar as well+--+newtype Stream m a =+ MkStream (forall r.+ State Stream m a -- state+ -> (a -> Stream m a -> m r) -- yield+ -> (a -> m r) -- singleton+ -> m r -- stop+ -> m r+ )++------------------------------------------------------------------------------+-- Types that can behave as a Stream+------------------------------------------------------------------------------++infixr 5 `consM`+infixr 5 |:++-- XXX Use a different SVar based on the stream type. But we need to make sure+-- that we do not lose performance due to polymorphism.+--+-- | Class of types that can represent a stream of elements of some type 'a' in+-- some monad 'm'.+--+-- @since 0.2.0+class+#if __GLASGOW_HASKELL__ >= 806+ ( forall m a. MonadAsync m => Semigroup (t m a)+ , forall m a. MonadAsync m => Monoid (t m a)+ , forall m. Monad m => Functor (t m)+ , forall m. MonadAsync m => Applicative (t m)+ ) =>+#endif+ IsStream t where+ toStream :: t m a -> Stream m a+ fromStream :: Stream m a -> t m a+ -- | Constructs a stream by adding a monadic action at the head of an+ -- existing stream. For example:+ --+ -- @+ -- > toList $ getLine \`consM` getLine \`consM` nil+ -- hello+ -- world+ -- ["hello","world"]+ -- @+ --+ -- /Concurrent (do not use 'parallely' to construct infinite streams)/+ --+ -- @since 0.2.0+ consM :: MonadAsync m => m a -> t m a -> t m a+ -- | Operator equivalent of 'consM'. We can read it as "@parallel colon@"+ -- to remember that @|@ comes before ':'.+ --+ -- @+ -- > toList $ getLine |: getLine |: nil+ -- hello+ -- world+ -- ["hello","world"]+ -- @+ --+ -- @+ -- let delay = threadDelay 1000000 >> print 1+ -- drain $ serially $ delay |: delay |: delay |: nil+ -- drain $ parallely $ delay |: delay |: delay |: nil+ -- @+ --+ -- /Concurrent (do not use 'parallely' to construct infinite streams)/+ --+ -- @since 0.2.0+ (|:) :: MonadAsync m => m a -> t m a -> t m a+ -- We can define (|:) just as 'consM' but it is defined explicitly for each+ -- type because we want to use SPECIALIZE pragma on the definition.++-- | Same as 'IsStream'.+--+-- @since 0.1.0+{-# DEPRECATED Streaming "Please use IsStream instead." #-}+type Streaming = IsStream++-------------------------------------------------------------------------------+-- Type adapting combinators+-------------------------------------------------------------------------------++-- XXX Move/reset the State here by reconstructing the stream with cleared+-- state. Can we make sure we do not do that when t1 = t2? If we do this then+-- we do not need to do that explicitly using svarStyle. It would act as+-- unShare when the stream type is the same.+--+-- | Adapt any specific stream type to any other specific stream type.+--+-- @since 0.1.0+adapt :: (IsStream t1, IsStream t2) => t1 m a -> t2 m a+adapt = fromStream . toStream++------------------------------------------------------------------------------+-- Building a stream+------------------------------------------------------------------------------++-- XXX The State is always parameterized by "Stream" which means State is not+-- different for different stream types. So we have to manually make sure that+-- when converting from one stream to another we migrate the state correctly.+-- This can be fixed if we use a different SVar type for different streams.+-- Currently we always use "SVar Stream" and therefore a different State type+-- parameterized by that stream.+--+-- XXX Since t is coercible we should be able to coerce k+-- mkStream k = fromStream $ MkStream $ coerce k+--+-- | Build a stream from an 'SVar', a stop continuation, a singleton stream+-- continuation and a yield continuation.+{-# INLINE_EARLY mkStream #-}+mkStream :: IsStream t+ => (forall r. State Stream m a+ -> (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r)+ -> t m a+mkStream k = fromStream $ MkStream $ \st yld sng stp ->+ let yieldk a r = yld a (toStream r)+ in k st yieldk sng stp++{-# RULES "mkStream from stream" mkStream = mkStreamFromStream #-}+mkStreamFromStream :: IsStream t+ => (forall r. State Stream m a+ -> (a -> Stream m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r)+ -> t m a+mkStreamFromStream k = fromStream $ MkStream k++{-# RULES "mkStream stream" mkStream = mkStreamStream #-}+mkStreamStream+ :: (forall r. State Stream m a+ -> (a -> Stream m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r)+ -> Stream m a+mkStreamStream = MkStream++-- | A terminal function that has no continuation to follow.+type StopK m = forall r. m r -> m r++-- | A monadic continuation, it is a function that yields a value of type "a"+-- and calls the argument (a -> m r) as a continuation with that value. We can+-- also think of it as a callback with a handler (a -> m r). Category+-- theorists call it a codensity type, a special type of right kan extension.+type YieldK m a = forall r. (a -> m r) -> m r++_wrapM :: Monad m => m a -> YieldK m a+_wrapM m = \k -> m >>= k++-- | Make an empty stream from a stop function.+fromStopK :: IsStream t => StopK m -> t m a+fromStopK k = mkStream $ \_ _ _ stp -> k stp++-- | Make a singleton stream from a yield function.+fromYieldK :: IsStream t => YieldK m a -> t m a+fromYieldK k = mkStream $ \_ _ sng _ -> k sng++-- | Add a yield function at the head of the stream.+consK :: IsStream t => YieldK m a -> t m a -> t m a+consK k r = mkStream $ \_ yld _ _ -> k (\x -> yld x r)++-- XXX Build a stream from a repeating callback function.++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++infixr 5 `cons`++-- faster than consM because there is no bind.+-- | Construct a stream by adding a pure value at the head of an existing+-- stream. For serial streams this is the same as @(return a) \`consM` r@ but+-- more efficient. For concurrent streams this is not concurrent whereas+-- 'consM' is concurrent. For example:+--+-- @+-- > toList $ 1 \`cons` 2 \`cons` 3 \`cons` nil+-- [1,2,3]+-- @+--+-- @since 0.1.0+{-# INLINE_NORMAL cons #-}+cons :: IsStream t => a -> t m a -> t m a+cons a r = mkStream $ \_ yld _ _ -> yld a r++infixr 5 .:++-- | Operator equivalent of 'cons'.+--+-- @+-- > toList $ 1 .: 2 .: 3 .: nil+-- [1,2,3]+-- @+--+-- @since 0.1.1+{-# INLINE (.:) #-}+(.:) :: IsStream t => a -> t m a -> t m a+(.:) = cons++-- | An empty stream.+--+-- @+-- > toList nil+-- []+-- @+--+-- @since 0.1.0+{-# INLINE_NORMAL nil #-}+nil :: IsStream t => t m a+nil = mkStream $ \_ _ _ stp -> stp++-- | An empty stream producing a side effect.+--+-- @+-- > toList (nilM (print "nil"))+-- "nil"+-- []+-- @+--+-- /Internal/+{-# INLINE_NORMAL nilM #-}+nilM :: (IsStream t, Monad m) => m b -> t m a+nilM m = mkStream $ \_ _ _ stp -> m >> stp++{-# INLINE_NORMAL yield #-}+yield :: IsStream t => a -> t m a+yield a = mkStream $ \_ _ single _ -> single a++{-# INLINE_NORMAL yieldM #-}+yieldM :: (Monad m, IsStream t) => m a -> t m a+yieldM m = fromStream $ mkStream $ \_ _ single _ -> m >>= single++-- XXX specialize to IO?+{-# INLINE consMBy #-}+consMBy :: (IsStream t, MonadAsync m) => (t m a -> t m a -> t m a)+ -> m a -> t m a -> t m a+consMBy f m r = (fromStream $ yieldM m) `f` r++------------------------------------------------------------------------------+-- Folding a stream+------------------------------------------------------------------------------++-- | Fold a stream by providing an SVar, a stop continuation, a singleton+-- continuation and a yield continuation. The stream would share the current+-- SVar passed via the State.+{-# INLINE_EARLY foldStreamShared #-}+foldStreamShared+ :: IsStream t+ => State Stream m a+ -> (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> t m a+ -> m r+foldStreamShared st yld sng stp m =+ let yieldk a x = yld a (fromStream x)+ MkStream k = toStream m+ in k st yieldk sng stp++-- XXX write a similar rule for foldStream as well?+{-# RULES "foldStreamShared from stream"+ foldStreamShared = foldStreamSharedStream #-}+foldStreamSharedStream+ :: State Stream m a+ -> (a -> Stream m a -> m r)+ -> (a -> m r)+ -> m r+ -> Stream m a+ -> m r+foldStreamSharedStream st yld sng stp m =+ let MkStream k = toStream m+ in k st yld sng stp++-- | Fold a stream by providing a State, stop continuation, a singleton+-- continuation and a yield continuation. The stream will not use the SVar+-- passed via State.+{-# INLINE foldStream #-}+foldStream+ :: IsStream t+ => State Stream m a+ -> (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> t m a+ -> m r+foldStream st yld sng stp m =+ let yieldk a x = yld a (fromStream x)+ MkStream k = toStream m+ in k (adaptState st) yieldk sng stp++-------------------------------------------------------------------------------+-- Instances+-------------------------------------------------------------------------------++-- NOTE: specializing the function outside the instance definition seems to+-- improve performance quite a bit at times, even if we have the same+-- SPECIALIZE in the instance definition.+{-# INLINE consMStream #-}+{-# SPECIALIZE consMStream :: IO a -> Stream IO a -> Stream IO a #-}+consMStream :: (Monad m) => m a -> Stream m a -> Stream m a+consMStream m r = MkStream $ \_ yld _ _ -> m >>= \a -> yld a r++-------------------------------------------------------------------------------+-- IsStream Stream+-------------------------------------------------------------------------------++instance IsStream Stream where+ toStream = id+ fromStream = id++ {-# INLINE consM #-}+ {-# SPECIALIZE consM :: IO a -> Stream IO a -> Stream IO a #-}+ consM :: Monad m => m a -> Stream m a -> Stream m a+ consM = consMStream++ {-# INLINE (|:) #-}+ {-# SPECIALIZE (|:) :: IO a -> Stream IO a -> Stream IO a #-}+ (|:) :: Monad m => m a -> Stream m a -> Stream m a+ (|:) = consMStream++-------------------------------------------------------------------------------+-- foldr/build fusion+-------------------------------------------------------------------------------++-- XXX perhaps we can just use foldrSM/buildM everywhere as they are more+-- general and cover foldrS/buildS as well.++-- | The function 'f' decides how to reconstruct the stream. We could+-- reconstruct using a shared state (SVar) or without sharing the state.+--+{-# INLINE foldrSWith #-}+foldrSWith :: IsStream t+ => (forall r. State Stream m b+ -> (b -> t m b -> m r)+ -> (b -> m r)+ -> m r+ -> t m b+ -> m r)+ -> (a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrSWith f step final m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let run x = f st yld sng stp x+ stop = run final+ single a = run $ step a final+ yieldk a r = run $ step a (go r)+ -- XXX if type a and b are the same we do not need adaptState, can we+ -- save some perf with that?+ -- XXX since we are using adaptState anyway here we can use+ -- foldStreamShared instead, will that save some perf?+ in foldStream (adaptState st) yieldk single stop m1++-- XXX we can use rewrite rules just for foldrSWith, if the function f is the+-- same we can rewrite it.++-- | Fold sharing the SVar state within the reconstructed stream+{-# INLINE_NORMAL foldrSShared #-}+foldrSShared :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrSShared = foldrSWith foldStreamShared++-- XXX consM is a typeclass method, therefore rewritten already. Instead maybe+-- we can make consM polymorphic using rewrite rules.+-- {-# RULES "foldrSShared/id" foldrSShared consM nil = \x -> x #-}+{-# RULES "foldrSShared/nil"+ forall k z. foldrSShared k z nil = z #-}+{-# RULES "foldrSShared/single"+ forall k z x. foldrSShared k z (yield x) = k x z #-}+-- {-# RULES "foldrSShared/app" [1]+-- forall ys. foldrSShared consM ys = \xs -> xs `conjoin` ys #-}++-- | Lazy right associative fold to a stream.+{-# INLINE_NORMAL foldrS #-}+foldrS :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrS = foldrSWith foldStream++{-# RULES "foldrS/id" foldrS cons nil = \x -> x #-}+{-# RULES "foldrS/nil" forall k z. foldrS k z nil = z #-}+-- See notes in GHC.Base about this rule+-- {-# RULES "foldr/cons"+-- forall k z x xs. foldrS k z (x `cons` xs) = k x (foldrS k z xs) #-}+{-# RULES "foldrS/single" forall k z x. foldrS k z (yield x) = k x z #-}+-- {-# RULES "foldrS/app" [1]+-- forall ys. foldrS cons ys = \xs -> xs `conjoin` ys #-}++-------------------------------------------------------------------------------+-- foldrS with monadic cons i.e. consM+-------------------------------------------------------------------------------++{-# INLINE foldrSMWith #-}+foldrSMWith :: (IsStream t, Monad m)+ => (forall r. State Stream m b+ -> (b -> t m b -> m r)+ -> (b -> m r)+ -> m r+ -> t m b+ -> m r)+ -> (m a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrSMWith f step final m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let run x = f st yld sng stp x+ stop = run final+ single a = run $ step (return a) final+ yieldk a r = run $ step (return a) (go r)+ in foldStream (adaptState st) yieldk single stop m1++{-# INLINE_NORMAL foldrSM #-}+foldrSM :: (IsStream t, Monad m)+ => (m a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrSM = foldrSMWith foldStream++-- {-# RULES "foldrSM/id" foldrSM consM nil = \x -> x #-}+{-# RULES "foldrSM/nil" forall k z. foldrSM k z nil = z #-}+{-# RULES "foldrSM/single" forall k z x. foldrSM k z (yieldM x) = k x z #-}+-- {-# RULES "foldrSM/app" [1]+-- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}++-- Like foldrSM but sharing the SVar state within the recostructed stream.+{-# INLINE_NORMAL foldrSMShared #-}+foldrSMShared :: (IsStream t, Monad m)+ => (m a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrSMShared = foldrSMWith foldStreamShared++-- {-# RULES "foldrSM/id" foldrSM consM nil = \x -> x #-}+{-# RULES "foldrSMShared/nil"+ forall k z. foldrSMShared k z nil = z #-}+{-# RULES "foldrSMShared/single"+ forall k z x. foldrSMShared k z (yieldM x) = k x z #-}+-- {-# RULES "foldrSM/app" [1]+-- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}++-------------------------------------------------------------------------------+-- build+-------------------------------------------------------------------------------++{-# INLINE_NORMAL build #-}+build :: IsStream t => forall a. (forall b. (a -> b -> b) -> b -> b) -> t m a+build g = g cons nil++{-# RULES "foldrM/build"+ forall k z (g :: forall b. (a -> b -> b) -> b -> b).+ foldrM k z (build g) = g k z #-}++{-# RULES "foldrS/build"+ forall k z (g :: forall b. (a -> b -> b) -> b -> b).+ foldrS k z (build g) = g k z #-}++{-# RULES "foldrS/cons/build"+ forall k z x (g :: forall b. (a -> b -> b) -> b -> b).+ foldrS k z (x `cons` build g) = k x (g k z) #-}++{-# RULES "foldrSShared/build"+ forall k z (g :: forall b. (a -> b -> b) -> b -> b).+ foldrSShared k z (build g) = g k z #-}++{-# RULES "foldrSShared/cons/build"+ forall k z x (g :: forall b. (a -> b -> b) -> b -> b).+ foldrSShared k z (x `cons` build g) = k x (g k z) #-}++-- build a stream by applying cons and nil to a build function+{-# INLINE_NORMAL buildS #-}+buildS :: IsStream t => ((a -> t m a -> t m a) -> t m a -> t m a) -> t m a+buildS g = g cons nil++{-# RULES "foldrS/buildS"+ forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ foldrS k z (buildS g) = g k z #-}++{-# RULES "foldrS/cons/buildS"+ forall k z x (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ foldrS k z (x `cons` buildS g) = k x (g k z) #-}++{-# RULES "foldrSShared/buildS"+ forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ foldrSShared k z (buildS g) = g k z #-}++{-# RULES "foldrSShared/cons/buildS"+ forall k z x (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ foldrSShared k z (x `cons` buildS g) = k x (g k z) #-}++-- build a stream by applying consM and nil to a build function+{-# INLINE_NORMAL buildSM #-}+buildSM :: (IsStream t, MonadAsync m)+ => ((m a -> t m a -> t m a) -> t m a -> t m a) -> t m a+buildSM g = g consM nil++{-# RULES "foldrSM/buildSM"+ forall k z (g :: (m a -> t m a -> t m a) -> t m a -> t m a).+ foldrSM k z (buildSM g) = g k z #-}++{-# RULES "foldrSMShared/buildSM"+ forall k z (g :: (m a -> t m a -> t m a) -> t m a -> t m a).+ foldrSMShared k z (buildSM g) = g k z #-}++-- Disabled because this may not fire as consM is a class Op+{-+{-# RULES "foldrS/consM/buildSM"+ forall k z x (g :: (m a -> t m a -> t m a) -> t m a -> t m a)+ . foldrSM k z (x `consM` buildSM g)+ = k x (g k z)+#-}+-}++-- Build using monadic build functions (continuations) instead of+-- reconstructing a stream.+{-# INLINE_NORMAL buildM #-}+buildM :: (IsStream t, MonadAsync m)+ => (forall r. (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )+ -> t m a+buildM g = mkStream $ \st yld sng stp ->+ g (\a r -> foldStream st yld sng stp (return a `consM` r)) sng stp++-- | Like 'buildM' but shares the SVar state across computations.+{-# INLINE_NORMAL sharedM #-}+sharedM :: (IsStream t, MonadAsync m)+ => (forall r. (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )+ -> t m a+sharedM g = mkStream $ \st yld sng stp ->+ g (\a r -> foldStreamShared st yld sng stp (return a `consM` r)) sng stp++-------------------------------------------------------------------------------+-- augment+-------------------------------------------------------------------------------++{-# INLINE_NORMAL augmentS #-}+augmentS :: IsStream t+ => ((a -> t m a -> t m a) -> t m a -> t m a) -> t m a -> t m a+augmentS g xs = g cons xs++{-# RULES "augmentS/nil"+ forall (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ augmentS g nil = buildS g+ #-}++{-# RULES "foldrS/augmentS"+ forall k z xs (g :: (a -> t m a -> t m a) -> t m a -> t m a).+ foldrS k z (augmentS g xs) = g k (foldrS k z xs)+ #-}++{-# RULES "augmentS/buildS"+ forall (g :: (a -> t m a -> t m a) -> t m a -> t m a)+ (h :: (a -> t m a -> t m a) -> t m a -> t m a).+ augmentS g (buildS h) = buildS (\c n -> g c (h c n))+ #-}++{-# INLINE_NORMAL augmentSM #-}+augmentSM :: (IsStream t, MonadAsync m)+ => ((m a -> t m a -> t m a) -> t m a -> t m a) -> t m a -> t m a+augmentSM g xs = g consM xs++{-# RULES "augmentSM/nil"+ forall (g :: (m a -> t m a -> t m a) -> t m a -> t m a).+ augmentSM g nil = buildSM g+ #-}++{-# RULES "foldrSM/augmentSM"+ forall k z xs (g :: (m a -> t m a -> t m a) -> t m a -> t m a).+ foldrSM k z (augmentSM g xs) = g k (foldrSM k z xs)+ #-}++{-# RULES "augmentSM/buildSM"+ forall (g :: (m a -> t m a -> t m a) -> t m a -> t m a)+ (h :: (m a -> t m a -> t m a) -> t m a -> t m a).+ augmentSM g (buildSM h) = buildSM (\c n -> g c (h c n))+ #-}++-------------------------------------------------------------------------------+-- Experimental foldrM/buildM+-------------------------------------------------------------------------------++-- | Lazy right fold with a monadic step function.+{-# INLINE_NORMAL foldrM #-}+foldrM :: IsStream t => (a -> m b -> m b) -> m b -> t m a -> m b+foldrM step acc m = go m+ where+ go m1 =+ let stop = acc+ single a = step a acc+ yieldk a r = step a (go r)+ in foldStream defState yieldk single stop m1++{-# INLINE_NORMAL foldrMKWith #-}+foldrMKWith+ :: (State Stream m a+ -> (a -> t m a -> m b)+ -> (a -> m b)+ -> m b+ -> t m a+ -> m b)+ -> (a -> m b -> m b)+ -> m b+ -> ((a -> t m a -> m b) -> (a -> m b) -> m b -> m b)+ -> m b+foldrMKWith f step acc g = go g+ where+ go k =+ let stop = acc+ single a = step a acc+ yieldk a r = step a (go (\yld sng stp -> f defState yld sng stp r))+ in k yieldk single stop++{-+{-# RULES "foldrM/buildS"+ forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a)+ . foldrM k z (buildS g)+ = g k z+#-}+-}+-- XXX in which case will foldrM/buildM fusion be useful?+{-# RULES "foldrM/buildM"+ forall step acc (g :: (forall r.+ (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )).+ foldrM step acc (buildM g) = foldrMKWith foldStream step acc g+ #-}++{-# RULES "foldrM/sharedM"+ forall step acc (g :: (forall r.+ (a -> t m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )).+ foldrM step acc (sharedM g) = foldrMKWith foldStreamShared step acc g+ #-}++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++-- | Polymorphic version of the 'Semigroup' operation '<>' of 'SerialT'.+-- Appends two streams sequentially, yielding all elements from the first+-- stream, and then all elements from the second stream.+--+-- @since 0.2.0+{-# INLINE serial #-}+serial :: IsStream t => t m a -> t m a -> t m a+-- XXX This doubles the time of toNullAp benchmark, may not be fusing properly+-- serial xs ys = augmentS (\c n -> foldrS c n xs) ys+serial m1 m2 = go m1+ where+ go m = mkStream $ \st yld sng stp ->+ let stop = foldStream st yld sng stp m2+ single a = yld a m2+ yieldk a r = yld a (go r)+ in foldStream st yieldk single stop m++-- join/merge/append streams depending on consM+{-# INLINE conjoin #-}+conjoin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a+conjoin xs ys = augmentSM (\c n -> foldrSM c n xs) ys++instance Semigroup (Stream m a) where+ (<>) = serial++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance Monoid (Stream m a) where+ mempty = nil+ mappend = (<>)++-------------------------------------------------------------------------------+-- Functor+-------------------------------------------------------------------------------++#if __GLASGOW_HASKELL__ < 800+#define Type *+#endif+-- Note eta expanded+{-# INLINE_LATE mapFB #-}+mapFB :: forall (t :: (Type -> Type) -> Type -> Type) b m a.+ (b -> t m b -> t m b) -> (a -> b) -> a -> t m b -> t m b+mapFB c f = \x ys -> c (f x) ys+#undef Type++{-# RULES+"mapFB/mapFB" forall c f g. mapFB (mapFB c f) g = mapFB c (f . g)+"mapFB/id" forall c. mapFB c (\x -> x) = c+ #-}++{-# INLINE map #-}+map :: IsStream t => (a -> b) -> t m a -> t m b+map f xs = buildS (\c n -> foldrS (mapFB c f) n xs)++-- XXX This definition might potentially be more efficient, but the cost in the+-- benchmark is dominated by unfoldrM cost so we cannot correctly determine+-- differences in the mapping cost. We should perhaps deduct the cost of+-- unfoldrM from the benchmarks and then compare.+{-+map f m = go m+ where+ go m1 =+ mkStream $ \st yld sng stp ->+ let single = sng . f+ yieldk a r = yld (f a) (go r)+ in foldStream (adaptState st) yieldk single stp m1+-}++{-# INLINE_LATE mapMFB #-}+mapMFB :: Monad m => (m b -> t m b -> t m b) -> (a -> m b) -> m a -> t m b -> t m b+mapMFB c f = \x ys -> c (x >>= f) ys++{-# RULES+ "mapMFB/mapMFB" forall c f g. mapMFB (mapMFB c f) g = mapMFB c (f >=> g)+ #-}+-- XXX These rules may never fire because pure/return type class rules will+-- fire first.+{-+"mapMFB/pure" forall c. mapMFB c (\x -> pure x) = c+"mapMFB/return" forall c. mapMFB c (\x -> return x) = c+-}++-- Be careful when modifying this, this uses a consM (|:) deliberately to allow+-- other stream types to overload it.+{-# INLINE mapM #-}+mapM :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m b+mapM f = foldrSShared (\x xs -> f x `consM` xs) nil+-- See note under map definition above.+{-+mapM f m = go m+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a = f a >>= sng+ yieldk a r = foldStreamShared st yld sng stp $ f a |: go r+ in foldStream (adaptState st) yieldk single stp m1+ -}++-- This is experimental serial version supporting fusion.+--+-- XXX what if we do not want to fuse two concurrent mapMs?+-- XXX we can combine two concurrent mapM only if the SVar is of the same type+-- So for now we use it only for serial streams.+-- XXX fusion would be easier for monomoprhic stream types.+-- {-# RULES "mapM serial" mapM = mapMSerial #-}+{-# INLINE mapMSerial #-}+mapMSerial :: MonadAsync m => (a -> m b) -> Stream m a -> Stream m b+mapMSerial f xs = buildSM (\c n -> foldrSMShared (mapMFB c f) n xs)++-- XXX in fact use the Stream type everywhere and only use polymorphism in the+-- high level modules/prelude.+instance Monad m => Functor (Stream m) where+ fmap = map++-------------------------------------------------------------------------------+-- Transformers+-------------------------------------------------------------------------------++instance MonadTrans Stream where+ lift = yieldM++-------------------------------------------------------------------------------+-- Nesting+-------------------------------------------------------------------------------++-- | Detach a stream from an SVar+{-# INLINE unShare #-}+unShare :: IsStream t => t m a -> t m a+unShare x = mkStream $ \st yld sng stp ->+ foldStream st yld sng stp x++-- XXX This is just concatMapBy with arguments flipped. We need to keep this+-- instead of using a concatMap style definition because the bind+-- implementation in Async and WAsync streams show significant perf degradation+-- if the argument order is changed.+{-# INLINE bindWith #-}+bindWith+ :: IsStream t+ => (forall c. t m c -> t m c -> t m c)+ -> t m a+ -> (a -> t m b)+ -> t m b+bindWith par m1 f = go m1+ where+ go m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single a = foldShared $ unShare (f a)+ yieldk a r = foldShared $ unShare (f a) `par` go r+ in foldStream (adaptState st) yieldk single stp m++-- XXX express in terms of foldrS?+-- XXX can we use a different stream type for the generated stream being+-- falttened so that we can combine them differently and keep the resulting+-- stream different?+-- XXX do we need specialize to IO?+-- XXX can we optimize when c and a are same, by removing the forall using+-- rewrite rules with type applications?++-- | Perform a 'concatMap' using a specified concat strategy. The first+-- argument specifies a merge or concat function that is used to merge the+-- streams generated by the map function. For example, the concat function+-- could be 'serial', 'parallel', 'async', 'ahead' or any other zip or merge+-- function.+--+-- @since 0.7.0+{-# INLINE concatMapBy #-}+concatMapBy+ :: IsStream t+ => (forall c. t m c -> t m c -> t m c)+ -> (a -> t m b)+ -> t m a+ -> t m b+concatMapBy par f xs = bindWith par xs f++{-# INLINE concatMap #-}+concatMap :: IsStream t => (a -> t m b) -> t m a -> t m b+concatMap f m = fromStream $+ concatMapBy serial+ (\a -> adapt $ toStream $ f a)+ (adapt $ toStream m)++{-+-- Fused version.+-- XXX This fuses but when the stream is nil this performs poorly.+-- The filterAllOut benchmark degrades. Need to investigate and fix that.+{-# INLINE concatMap #-}+concatMap :: IsStream t => (a -> t m b) -> t m a -> t m b+concatMap f xs = buildS+ (\c n -> foldrS (\x b -> foldrS c b (f x)) n xs)++-- Stream polymorphic concatMap implementation+-- XXX need to use buildSM/foldrSMShared for parallel behavior+-- XXX unShare seems to degrade the fused performance+{-# INLINE_EARLY concatMap_ #-}+concatMap_ :: IsStream t => (a -> t m b) -> t m a -> t m b+concatMap_ f xs = buildS+ (\c n -> foldrSShared (\x b -> foldrSShared c b (unShare $ f x)) n xs)+-}++instance Monad m => Applicative (Stream m) where+ {-# INLINE pure #-}+ pure = yield+ {-# INLINE (<*>) #-}+ (<*>) = ap++-- NOTE: even though concatMap for StreamD is 3x faster compared to StreamK,+-- the monad instance of StreamD is slower than StreamK after foldr/build+-- fusion.+instance Monad m => Monad (Stream m) where+ {-# INLINE return #-}+ return = pure+ {-# INLINE (>>=) #-}+ (>>=) = flip concatMap++{-+-- Like concatMap but generates stream using an unfold function. Similar to+-- concatUnfold but for StreamK.+concatUnfoldr :: IsStream t+ => (b -> t m (Maybe (a, b))) -> t m b -> t m a+concatUnfoldr = undefined+-}
+ src/Streamly/Internal/Data/Stream/Zip.hs view
@@ -0,0 +1,266 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving#-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} -- XXX++-- |+-- Module : Streamly.Internal.Data.Stream.Zip+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+--+module Streamly.Internal.Data.Stream.Zip+ (+ ZipSerialM+ , ZipSerial+ , zipSerially++ , ZipAsyncM+ , ZipAsync+ , zipAsyncly++ , zipWith+ , zipWithM+ , zipAsyncWith+ , zipAsyncWithM++ -- * Deprecated+ , ZipStream+ , zipping+ , zippingAsync+ )+where++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData(..))+#if MIN_VERSION_deepseq(1,4,3)+import Control.DeepSeq (NFData1(..))+#endif+import Data.Foldable (Foldable(foldl'), fold)+import Data.Functor.Identity (Identity(..), runIdentity)+import Data.Maybe (fromMaybe)+import Data.Semigroup (Endo(..))+#if __GLASGOW_HASKELL__ < 808+import Data.Semigroup (Semigroup(..))+#endif+import GHC.Exts (IsList(..), IsString(..))+import Text.Read (Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec,+ readListPrecDefault)+import Prelude hiding (map, repeat, zipWith, errorWithoutStackTrace)++import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace)+import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream)+import Streamly.Internal.Data.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.SVar (MonadAsync)++import qualified Streamly.Internal.Data.Stream.Prelude as P+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++#ifdef USE_STREAMK_ONLY+import qualified Streamly.Internal.Data.Stream.StreamK as S+#else+import qualified Streamly.Internal.Data.Stream.StreamD as S+#endif++#include "Instances.hs"++-- | Like 'zipWith' but using a monadic zipping function.+--+-- @since 0.4.0+{-# INLINABLE zipWithM #-}+zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c+zipWithM f m1 m2 = P.fromStreamS $ S.zipWithM f (P.toStreamS m1) (P.toStreamS m2)++-- | Zip two streams serially using a pure zipping function.+--+-- @+-- > S.toList $ S.zipWith (+) (S.fromList [1,2,3]) (S.fromList [4,5,6])+-- [5,7,9]+-- @+--+-- @since 0.1.0+{-# INLINABLE zipWith #-}+zipWith :: (IsStream t, Monad m) => (a -> b -> c) -> t m a -> t m b -> t m c+zipWith f m1 m2 = P.fromStreamS $ S.zipWith f (P.toStreamS m1) (P.toStreamS m2)++------------------------------------------------------------------------------+-- Parallel Zipping+------------------------------------------------------------------------------++-- | Like 'zipWithM' but zips concurrently i.e. both the streams being zipped+-- are generated concurrently.+--+-- @since 0.4.0+{-# INLINE zipAsyncWithM #-}+zipAsyncWithM :: (IsStream t, MonadAsync m)+ => (a -> b -> m c) -> t m a -> t m b -> t m c+zipAsyncWithM f m1 m2 = D.fromStreamD $+ D.zipWithM f (D.mkParallelD $ D.toStreamD m1)+ (D.mkParallelD $ D.toStreamD m2)++-- | Like 'zipWith' but zips concurrently i.e. both the streams being zipped+-- are generated concurrently.+--+-- @since 0.1.0+{-# INLINE zipAsyncWith #-}+zipAsyncWith :: (IsStream t, MonadAsync m)+ => (a -> b -> c) -> t m a -> t m b -> t m c+zipAsyncWith f = zipAsyncWithM (\a b -> return (f a b))++------------------------------------------------------------------------------+-- Serially Zipping Streams+------------------------------------------------------------------------------++-- | The applicative instance of 'ZipSerialM' zips a number of streams serially+-- i.e. it produces one element from each stream serially and then zips all+-- those elements.+--+-- @+-- main = (toList . 'zipSerially' $ (,,) \<$\> s1 \<*\> s2 \<*\> s3) >>= print+-- where s1 = fromFoldable [1, 2]+-- s2 = fromFoldable [3, 4]+-- s3 = fromFoldable [5, 6]+-- @+-- @+-- [(1,3,5),(2,4,6)]+-- @+--+-- The 'Semigroup' instance of this type works the same way as that of+-- 'SerialT'.+--+-- @since 0.2.0+newtype ZipSerialM m a = ZipSerialM {getZipSerialM :: Stream m a}+ deriving (Semigroup, Monoid)++-- |+-- @since 0.1.0+{-# DEPRECATED ZipStream "Please use 'ZipSerialM' instead." #-}+type ZipStream = ZipSerialM++-- | An IO stream whose applicative instance zips streams serially.+--+-- @since 0.2.0+type ZipSerial = ZipSerialM IO++-- | Fix the type of a polymorphic stream as 'ZipSerialM'.+--+-- @since 0.2.0+zipSerially :: IsStream t => ZipSerialM m a -> t m a+zipSerially = K.adapt++-- | Same as 'zipSerially'.+--+-- @since 0.1.0+{-# DEPRECATED zipping "Please use zipSerially instead." #-}+zipping :: IsStream t => ZipSerialM m a -> t m a+zipping = zipSerially++consMZip :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a+consMZip m ms = fromStream $ K.consMStream m (toStream ms)++instance IsStream ZipSerialM where+ toStream = getZipSerialM+ fromStream = ZipSerialM++ {-# INLINE consM #-}+ {-# SPECIALIZE consM :: IO a -> ZipSerialM IO a -> ZipSerialM IO a #-}+ consM :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a+ consM = consMZip++ {-# INLINE (|:) #-}+ {-# SPECIALIZE (|:) :: IO a -> ZipSerialM IO a -> ZipSerialM IO a #-}+ (|:) :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a+ (|:) = consMZip++LIST_INSTANCES(ZipSerialM)+NFDATA1_INSTANCE(ZipSerialM)++instance Monad m => Functor (ZipSerialM m) where+ {-# INLINE fmap #-}+ fmap f (ZipSerialM m) = D.fromStreamD $ D.mapM (return . f) $ D.toStreamD m++instance Monad m => Applicative (ZipSerialM m) where+ pure = ZipSerialM . K.repeat+ {-# INLINE (<*>) #-}+ (<*>) = zipWith id++FOLDABLE_INSTANCE(ZipSerialM)+TRAVERSABLE_INSTANCE(ZipSerialM)++------------------------------------------------------------------------------+-- Parallely Zipping Streams+------------------------------------------------------------------------------+--+-- | Like 'ZipSerialM' but zips in parallel, it generates all the elements to+-- be zipped concurrently.+--+-- @+-- main = (toList . 'zipAsyncly' $ (,,) \<$\> s1 \<*\> s2 \<*\> s3) >>= print+-- where s1 = fromFoldable [1, 2]+-- s2 = fromFoldable [3, 4]+-- s3 = fromFoldable [5, 6]+-- @+-- @+-- [(1,3,5),(2,4,6)]+-- @+--+-- The 'Semigroup' instance of this type works the same way as that of+-- 'SerialT'.+--+-- @since 0.2.0+newtype ZipAsyncM m a = ZipAsyncM {getZipAsyncM :: Stream m a}+ deriving (Semigroup, Monoid)++-- | An IO stream whose applicative instance zips streams wAsyncly.+--+-- @since 0.2.0+type ZipAsync = ZipAsyncM IO++-- | Fix the type of a polymorphic stream as 'ZipAsyncM'.+--+-- @since 0.2.0+zipAsyncly :: IsStream t => ZipAsyncM m a -> t m a+zipAsyncly = K.adapt++-- | Same as 'zipAsyncly'.+--+-- @since 0.1.0+{-# DEPRECATED zippingAsync "Please use zipAsyncly instead." #-}+zippingAsync :: IsStream t => ZipAsyncM m a -> t m a+zippingAsync = zipAsyncly++consMZipAsync :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a+consMZipAsync m ms = fromStream $ K.consMStream m (toStream ms)++instance IsStream ZipAsyncM where+ toStream = getZipAsyncM+ fromStream = ZipAsyncM++ {-# INLINE consM #-}+ {-# SPECIALIZE consM :: IO a -> ZipAsyncM IO a -> ZipAsyncM IO a #-}+ consM :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a+ consM = consMZipAsync++ {-# INLINE (|:) #-}+ {-# SPECIALIZE (|:) :: IO a -> ZipAsyncM IO a -> ZipAsyncM IO a #-}+ (|:) :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a+ (|:) = consMZipAsync++instance Monad m => Functor (ZipAsyncM m) where+ {-# INLINE fmap #-}+ fmap f (ZipAsyncM m) = D.fromStreamD $ D.mapM (return . f) $ D.toStreamD m++instance MonadAsync m => Applicative (ZipAsyncM m) where+ pure = ZipAsyncM . K.repeat+ {-# INLINE (<*>) #-}+ m1 <*> m2 = zipAsyncWith id m1 m2
src/Streamly/Internal/Data/Strict.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_HADDOCK hide #-}- -- | -- Module : Streamly.Internal.Data.Strict -- Copyright : (c) 2019 Composewell Technologies@@ -25,7 +23,7 @@ , Tuple3' (..) , Tuple4' (..) , Maybe' (..)- , fromStrictMaybe+ , toMaybe , Either' (..) ) where@@ -48,10 +46,10 @@ -- XXX perhaps we can use a type class having fromStrict/toStrict operations. -- -- | Convert strict Maybe' to lazy Maybe-{-# INLINABLE fromStrictMaybe #-}-fromStrictMaybe :: Monad m => Maybe' a -> m (Maybe a)-fromStrictMaybe Nothing' = return $ Nothing-fromStrictMaybe (Just' a) = return $ Just a+{-# INLINABLE toMaybe #-}+toMaybe :: Maybe' a -> Maybe a+toMaybe Nothing' = Nothing+toMaybe (Just' a) = Just a ------------------------------------------------------------------------------- -- Either
src/Streamly/Internal/Data/Time.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_HADDOCK hide #-}- -- | -- Module : Streamly.Internal.Data.Time -- Copyright : (c) 2017 Harendra Kumar
src/Streamly/Internal/Data/Time/Clock.hsc view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
src/Streamly/Internal/Data/Time/Units.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-}
src/Streamly/Internal/Data/Unfold.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-}@@ -87,6 +86,7 @@ , identity , const , replicateM+ , repeatM , fromList , fromListM , enumerateFromStepIntegral@@ -112,16 +112,22 @@ -- * Exceptions , gbracket+ , gbracketIO , before , after+ , afterIO , onException , finally+ , finallyIO , bracket+ , bracketIO , handle ) where import Control.Exception (Exception)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Trans.Control (MonadBaseControl) import Data.Void (Void) import GHC.Types (SPEC(..)) import Prelude hiding (concat, map, mapM, takeWhile, take, filter, const)@@ -132,14 +138,14 @@ #endif import Streamly.Internal.Data.Unfold.Types (Unfold(..)) import Streamly.Internal.Data.Fold.Types (Fold(..))-import Streamly.Internal.Data.SVar (defState)+import Streamly.Internal.Data.SVar (defState, MonadAsync) import Control.Monad.Catch (MonadCatch) import qualified Prelude import qualified Control.Monad.Catch as MC import qualified Data.Tuple as Tuple-import qualified Streamly.Streams.StreamK as K-import qualified Streamly.Streams.StreamD as D+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D ------------------------------------------------------------------------------- -- Input operations@@ -404,6 +410,15 @@ then Stop else Yield x (x, (i - 1)) +-- | Generates an infinite stream repeating the seed.+--+{-# INLINE repeatM #-}+repeatM :: Monad m => Unfold m a a+repeatM = Unfold step return+ where+ {-# INLINE_LATE step #-}+ step x = return $ Yield x x+ -- | Convert a list of pure values to a 'Stream' {-# INLINE_LATE fromList #-} fromList :: Monad m => Unfold m [a] a@@ -644,6 +659,53 @@ Skip s -> return $ Skip (Left s) Stop -> return Stop +-- | The most general bracketing and exception combinator. All other+-- combinators can be expressed in terms of this combinator. This can also be+-- used for cases which are not covered by the standard combinators.+--+-- /Internal/+--+{-# INLINE_NORMAL gbracketIO #-}+gbracketIO+ :: (MonadIO m, MonadBaseControl IO m)+ => (a -> m c) -- ^ before+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)+ -> (c -> m d) -- ^ after, on normal stop, or GC+ -> Unfold m (c, e) b -- ^ on exception+ -> Unfold m c b -- ^ unfold to run+ -> Unfold m a b+gbracketIO bef exc aft (Unfold estep einject) (Unfold step1 inject1) =+ Unfold step inject++ where++ inject x = do+ r <- bef x+ ref <- D.newFinalizedIORef (aft r)+ s <- inject1 r+ return $ Right (s, r, ref)++ {-# INLINE_LATE step #-}+ step (Right (st, v, ref)) = do+ res <- exc $ step1 st+ case res of+ Right r -> case r of+ Yield x s -> return $ Yield x (Right (s, v, ref))+ Skip s -> return $ Skip (Right (s, v, ref))+ Stop -> do+ D.runIORefFinalizer ref+ return Stop+ Left e -> do+ D.clearIORefFinalizer ref+ r <- einject (v, e)+ return $ Skip (Left r)+ step (Left st) = do+ res <- estep st+ case res of+ Yield x s -> return $ Yield x (Left s)+ Skip s -> return $ Skip (Left s)+ Stop -> return Stop+ -- The custom implementation of "before" is slightly faster (5-7%) than -- "_before". This is just to document and make sure that we can always use -- gbracket to implement before. The same applies to other combinators as well.@@ -681,6 +743,10 @@ -- | Run a side effect whenever the unfold stops normally. --+-- Prefer afterIO over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+-- -- /Internal/ {-# INLINE_NORMAL after #-} after :: Monad m => (a -> m c) -> Unfold m a b -> Unfold m a b@@ -700,6 +766,32 @@ Skip s -> return $ Skip (s, v) Stop -> action v >> return Stop +-- | Run a side effect whenever the unfold stops normally+-- or is garbage collected after a partial lazy evaluation.+--+-- /Internal/+{-# INLINE_NORMAL afterIO #-}+afterIO :: (MonadIO m, MonadBaseControl IO m)+ => (a -> m c) -> Unfold m a b -> Unfold m a b+afterIO action (Unfold step1 inject1) = Unfold step inject++ where++ inject x = do+ s <- inject1 x+ ref <- D.newFinalizedIORef (action x)+ return (s, ref)++ {-# INLINE_LATE step #-}+ step (st, ref) = do+ res <- step1 st+ case res of+ Yield x s -> return $ Yield x (s, ref)+ Skip s -> return $ Skip (s, ref)+ Stop -> do+ D.runIORefFinalizer ref+ return Stop+ {-# INLINE_NORMAL _onException #-} _onException :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b _onException action unf =@@ -737,6 +829,10 @@ -- | Run a side effect whenever the unfold stops normally or aborts due to an -- exception. --+-- Prefer finallyIO over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+-- -- /Internal/ {-# INLINE_NORMAL finally #-} finally :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b@@ -756,6 +852,32 @@ Skip s -> return $ Skip (s, v) Stop -> action v >> return Stop +-- | Run a side effect whenever the unfold stops normally, aborts due to an+-- exception or if it is garbage collected after a partial lazy evaluation.+--+-- /Internal/+{-# INLINE_NORMAL finallyIO #-}+finallyIO :: (MonadAsync m, MonadCatch m)+ => (a -> m c) -> Unfold m a b -> Unfold m a b+finallyIO action (Unfold step1 inject1) = Unfold step inject++ where++ inject x = do+ s <- inject1 x+ ref <- D.newFinalizedIORef (action x)+ return (s, ref)++ {-# INLINE_LATE step #-}+ step (st, ref) = do+ res <- step1 st `MC.onException` D.runIORefFinalizer ref+ case res of+ Yield x s -> return $ Yield x (s, ref)+ Skip s -> return $ Skip (s, ref)+ Stop -> do+ D.runIORefFinalizer ref+ return Stop+ {-# INLINE_NORMAL _bracket #-} _bracket :: MonadCatch m => (a -> m c) -> (c -> m d) -> Unfold m c b -> Unfold m a b@@ -768,6 +890,10 @@ -- if an exception occurs then the @after@ action is run with the output of -- @before@ as argument. --+-- Prefer bracketIO over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+-- -- /Internal/ {-# INLINE_NORMAL bracket #-} bracket :: MonadCatch m@@ -788,6 +914,36 @@ Yield x s -> return $ Yield x (s, v) Skip s -> return $ Skip (s, v) Stop -> aft v >> return Stop++-- | @bracket before after between@ runs the @before@ action and then unfolds+-- its output using the @between@ unfold. When the @between@ unfold is done or+-- if an exception occurs then the @after@ action is run with the output of+-- @before@ as argument. The after action is also executed if the unfold is+-- paritally evaluated and then garbage collected.+--+-- /Internal/+{-# INLINE_NORMAL bracketIO #-}+bracketIO :: (MonadAsync m, MonadCatch m)+ => (a -> m c) -> (c -> m d) -> Unfold m c b -> Unfold m a b+bracketIO bef aft (Unfold step1 inject1) = Unfold step inject++ where++ inject x = do+ r <- bef x+ s <- inject1 r+ ref <- D.newFinalizedIORef (aft r)+ return (s, ref)++ {-# INLINE_LATE step #-}+ step (st, ref) = do+ res <- step1 st `MC.onException` D.runIORefFinalizer ref+ case res of+ Yield x s -> return $ Yield x (s, ref)+ Skip s -> return $ Skip (s, ref)+ Stop -> do+ D.runIORefFinalizer ref+ return Stop -- | When unfolding if an exception occurs, unfold the exception using the -- exception unfold supplied as the first argument to 'handle'.
src/Streamly/Internal/Data/Unfold/Types.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-}
src/Streamly/Internal/Data/Unicode/Char.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE FlexibleContexts #-} -- |
src/Streamly/Internal/Data/Unicode/Stream.hs view
@@ -1,24 +1,32 @@-{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-} -- | -- Module : Streamly.Data.Internal.Unicode.Stream -- Copyright : (c) 2018 Composewell Technologies+-- (c) Bjoern Hoehrmann 2008-2009 -- -- License : BSD3 -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC --++#include "inline.hs"+ module Streamly.Internal.Data.Unicode.Stream ( -- * Construction (Decoding) decodeLatin1 , decodeUtf8 , decodeUtf8Lax- , D.DecodeError(..)- , D.DecodeState- , D.CodePoint+ , DecodeError(..)+ , DecodeState+ , CodePoint , decodeUtf8Either , resumeDecodeUtf8Either , decodeUtf8Arrays@@ -33,6 +41,16 @@ , strip -- (dropAround isSpace) , stripEnd -}++ -- * StreamD UTF8 Encoding / Decoding transformations.+ , decodeUtf8D+ , encodeUtf8D+ , decodeUtf8LenientD+ , decodeUtf8EitherD+ , resumeDecodeUtf8EitherD+ , decodeUtf8ArraysD+ , decodeUtf8ArraysLenientD+ -- * Transformation , stripStart , lines@@ -42,23 +60,519 @@ ) where -import Control.Monad.IO.Class (MonadIO)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Bits (shiftR, shiftL, (.|.), (.&.)) import Data.Char (ord) import Data.Word (Word8)-import GHC.Base (unsafeChr)-import Streamly (IsStream)+import Foreign.ForeignPtr (touchForeignPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)+import Foreign.Storable (Storable(..))+import GHC.Base (assert, unsafeChr)+import GHC.ForeignPtr (ForeignPtr (..))+import GHC.IO.Encoding.Failure (isSurrogate)+import GHC.Ptr (Ptr (..), plusPtr) import Prelude hiding (String, lines, words, unlines, unwords)+import System.IO.Unsafe (unsafePerformIO)++import Streamly (IsStream) import Streamly.Data.Fold (Fold) import Streamly.Memory.Array (Array) import Streamly.Internal.Data.Unfold (Unfold)+import Streamly.Internal.Data.SVar (adaptState)+import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))+import Streamly.Internal.Data.Strict (Tuple'(..)) +#if __GLASGOW_HASKELL__ < 800+import Streamly.Internal.Data.Stream.StreamD (pattern Stream)+#endif++import qualified Streamly.Internal.Memory.Array.Types as A import qualified Streamly.Internal.Prelude as S-import qualified Streamly.Streams.StreamD as D+import qualified Streamly.Internal.Data.Stream.StreamD as D ---------------------------------------------------------------------------------- Encoding/Decoding Unicode Characters+-- Encoding/Decoding Unicode (UTF-8) Characters ------------------------------------------------------------------------------- +-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.++data WList = WCons !Word8 !WList | WNil++{-# INLINE ord2 #-}+ord2 :: Char -> WList+ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))+ where+ n = ord c+ x1 = fromIntegral $ (n `shiftR` 6) + 0xC0+ x2 = fromIntegral $ (n .&. 0x3F) + 0x80++{-# INLINE ord3 #-}+ord3 :: Char -> WList+ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))+ where+ n = ord c+ x1 = fromIntegral $ (n `shiftR` 12) + 0xE0+ x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80+ x3 = fromIntegral $ (n .&. 0x3F) + 0x80++{-# INLINE ord4 #-}+ord4 :: Char -> WList+ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))+ where+ n = ord c+ x1 = fromIntegral $ (n `shiftR` 18) + 0xF0+ x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80+ x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80+ x4 = fromIntegral $ (n .&. 0x3F) + 0x80++data CodingFailureMode+ = TransliterateCodingFailure+ | ErrorOnCodingFailure+ deriving (Show)++{-# INLINE replacementChar #-}+replacementChar :: Char+replacementChar = '\xFFFD'++-- Int helps in cheaper conversion from Int to Char+type CodePoint = Int+type DecodeState = Word8++-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.++-- XXX Use names decodeSuccess = 0, decodeFailure = 12++decodeTable :: [Word8]+decodeTable = [+ -- The first part of the table maps bytes to character classes that+ -- to reduce the size of the transition table and create bitmasks.+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,++ -- The second part is a transition table that maps a combination+ -- of a state of the automaton and a character class to a state.+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,+ 12,36,12,12,12,12,12,12,12,12,12,12+ ]++utf8d :: A.Array Word8+utf8d =+ unsafePerformIO+ -- Aligning to cacheline makes a barely noticeable difference+ -- XXX currently alignment is not implemented for unmanaged allocation+ $ D.runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))+ (D.fromList decodeTable)++-- | Return element at the specified index without checking the bounds.+-- and without touching the foreign ptr.+{-# INLINE_NORMAL unsafePeekElemOff #-}+unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a+unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x++-- decode is split into two separate cases to avoid branching instructions.+-- From the higher level flow we already know which case we are in so we can+-- call the appropriate decode function.+--+-- When the state is 0+{-# INLINE decode0 #-}+decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint+decode0 table byte =+ let !t = table `unsafePeekElemOff` fromIntegral byte+ !codep' = (0xff `shiftR` (fromIntegral t)) .&. fromIntegral byte+ !state' = table `unsafePeekElemOff` (256 + fromIntegral t)+ in assert ((byte > 0x7f || error showByte)+ && (state' /= 0 || error (showByte ++ showTable)))+ (Tuple' state' codep')++ where++ utf8table =+ let !(Ptr addr) = table+ end = table `plusPtr` 364+ in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8+ showByte = "Streamly: decode0: byte: " ++ show byte+ showTable = " table: " ++ show utf8table++-- When the state is not 0+{-# INLINE decode1 #-}+decode1+ :: Ptr Word8+ -> DecodeState+ -> CodePoint+ -> Word8+ -> Tuple' DecodeState CodePoint+decode1 table state codep byte =+ -- Remember codep is Int type!+ -- Can it be unsafe to convert the resulting Int to Char?+ let !t = table `unsafePeekElemOff` fromIntegral byte+ !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)+ !state' = table `unsafePeekElemOff`+ (256 + fromIntegral state + fromIntegral t)+ in assert (codep' <= 0x10FFFF+ || error (showByte ++ showState state codep))+ (Tuple' state' codep')+ where++ utf8table =+ let !(Ptr addr) = table+ end = table `plusPtr` 364+ in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8+ showByte = "Streamly: decode1: byte: " ++ show byte+ showState st cp =+ " state: " ++ show st +++ " codepoint: " ++ show cp +++ " table: " ++ show utf8table++-- We can divide the errors in three general categories:+-- * A non-starter was encountered in a begin state+-- * A starter was encountered without completing a codepoint+-- * The last codepoint was not complete (input underflow)+--+data DecodeError = DecodeError !DecodeState !CodePoint deriving Show++data FreshPoint s a+ = FreshPointDecodeInit s+ | FreshPointDecodeInit1 s Word8+ | FreshPointDecodeFirst s Word8+ | FreshPointDecoding s !DecodeState !CodePoint+ | YieldAndContinue a (FreshPoint s a)+ | Done++-- XXX Add proper error messages+-- XXX Implement this in terms of decodeUtf8Either+{-# INLINE_NORMAL decodeUtf8WithD #-}+decodeUtf8WithD :: Monad m => CodingFailureMode -> Stream m Word8 -> Stream m Char+decodeUtf8WithD cfm (Stream step state) =+ let A.Array p _ _ = utf8d+ !ptr = (unsafeForeignPtrToPtr p)+ in Stream (step' ptr) (FreshPointDecodeInit state)+ where+ {-# INLINE transliterateOrError #-}+ transliterateOrError e s =+ case cfm of+ ErrorOnCodingFailure -> error e+ TransliterateCodingFailure -> YieldAndContinue replacementChar s+ {-# INLINE inputUnderflow #-}+ inputUnderflow =+ case cfm of+ ErrorOnCodingFailure ->+ error "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Input Underflow"+ TransliterateCodingFailure -> YieldAndContinue replacementChar Done+ {-# INLINE_LATE step' #-}+ step' _ gst (FreshPointDecodeInit st) = do+ r <- step (adaptState gst) st+ return $ case r of+ Yield x s -> Skip (FreshPointDecodeInit1 s x)+ Skip s -> Skip (FreshPointDecodeInit s)+ Stop -> Skip Done++ step' _ _ (FreshPointDecodeInit1 st x) = do+ -- Note: It is important to use a ">" instead of a "<=" test+ -- here for GHC to generate code layout for default branch+ -- prediction for the common case. This is fragile and might+ -- change with the compiler versions, we need a more reliable+ -- "likely" primitive to control branch predication.+ case x > 0x7f of+ False ->+ return $ Skip $ YieldAndContinue+ (unsafeChr (fromIntegral x))+ (FreshPointDecodeInit st)+ -- Using a separate state here generates a jump to a+ -- separate code block in the core which seems to perform+ -- slightly better for the non-ascii case.+ True -> return $ Skip $ FreshPointDecodeFirst st x++ -- XXX should we merge it with FreshPointDecodeInit1?+ step' table _ (FreshPointDecodeFirst st x) = do+ let (Tuple' sv cp) = decode0 table x+ return $+ case sv of+ 12 ->+ Skip $+ transliterateOrError+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"+ (FreshPointDecodeInit st)+ 0 -> error "unreachable state"+ _ -> Skip (FreshPointDecoding st sv cp)++ -- We recover by trying the new byte x a starter of a new codepoint.+ -- XXX need to use the same recovery in array decoding routine as well+ step' table gst (FreshPointDecoding st statePtr codepointPtr) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x+ return $+ case sv of+ 0 -> Skip $ YieldAndContinue (unsafeChr cp)+ (FreshPointDecodeInit s)+ 12 ->+ Skip $+ transliterateOrError+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"+ (FreshPointDecodeInit1 s x)+ _ -> Skip (FreshPointDecoding s sv cp)+ Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)+ Stop -> return $ Skip inputUnderflow++ step' _ _ (YieldAndContinue c s) = return $ Yield c s+ step' _ _ Done = return Stop++{-# INLINE decodeUtf8D #-}+decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char+decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure++{-# INLINE decodeUtf8LenientD #-}+decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char+decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure++{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}+resumeDecodeUtf8EitherD+ :: Monad m+ => DecodeState+ -> CodePoint+ -> Stream m Word8+ -> Stream m (Either DecodeError Char)+resumeDecodeUtf8EitherD dst codep (Stream step state) =+ let A.Array p _ _ = utf8d+ !ptr = (unsafeForeignPtrToPtr p)+ stt =+ if dst == 0+ then FreshPointDecodeInit state+ else FreshPointDecoding state dst codep+ in Stream (step' ptr) stt+ where+ {-# INLINE_LATE step' #-}+ step' _ gst (FreshPointDecodeInit st) = do+ r <- step (adaptState gst) st+ return $ case r of+ Yield x s -> Skip (FreshPointDecodeInit1 s x)+ Skip s -> Skip (FreshPointDecodeInit s)+ Stop -> Skip Done++ step' _ _ (FreshPointDecodeInit1 st x) = do+ -- Note: It is important to use a ">" instead of a "<=" test+ -- here for GHC to generate code layout for default branch+ -- prediction for the common case. This is fragile and might+ -- change with the compiler versions, we need a more reliable+ -- "likely" primitive to control branch predication.+ case x > 0x7f of+ False ->+ return $ Skip $ YieldAndContinue+ (Right $ unsafeChr (fromIntegral x))+ (FreshPointDecodeInit st)+ -- Using a separate state here generates a jump to a+ -- separate code block in the core which seems to perform+ -- slightly better for the non-ascii case.+ True -> return $ Skip $ FreshPointDecodeFirst st x++ -- XXX should we merge it with FreshPointDecodeInit1?+ step' table _ (FreshPointDecodeFirst st x) = do+ let (Tuple' sv cp) = decode0 table x+ return $+ case sv of+ 12 ->+ Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))+ (FreshPointDecodeInit st)+ 0 -> error "unreachable state"+ _ -> Skip (FreshPointDecoding st sv cp)++ -- We recover by trying the new byte x a starter of a new codepoint.+ -- XXX need to use the same recovery in array decoding routine as well+ step' table gst (FreshPointDecoding st statePtr codepointPtr) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x+ return $+ case sv of+ 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)+ (FreshPointDecodeInit s)+ 12 ->+ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)+ (FreshPointDecodeInit1 s x)+ _ -> Skip (FreshPointDecoding s sv cp)+ Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)+ Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done++ step' _ _ (YieldAndContinue c s) = return $ Yield c s+ step' _ _ Done = return Stop++{-# INLINE_NORMAL decodeUtf8EitherD #-}+decodeUtf8EitherD :: Monad m+ => Stream m Word8 -> Stream m (Either DecodeError Char)+decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0++data FlattenState s a+ = OuterLoop s !(Maybe (DecodeState, CodePoint))+ | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)+ | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8+ | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)+ !DecodeState !CodePoint+ | YAndC !Char (FlattenState s a) -- These constructors can be+ -- encoded in the FreshPoint+ -- type, I prefer to keep these+ -- flat even though that means+ -- coming up with new names+ | D++-- The normal decodeUtf8 above should fuse with flattenArrays+-- to create this exact code but it doesn't for some reason, as of now this+-- remains the fastest way I could figure out to decodeUtf8.+--+-- XXX Add Proper error messages+{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}+decodeUtf8ArraysWithD ::+ MonadIO m+ => CodingFailureMode+ -> Stream m (A.Array Word8)+ -> Stream m Char+decodeUtf8ArraysWithD cfm (Stream step state) =+ let A.Array p _ _ = utf8d+ !ptr = (unsafeForeignPtrToPtr p)+ in Stream (step' ptr) (OuterLoop state Nothing)+ where+ {-# INLINE transliterateOrError #-}+ transliterateOrError e s =+ case cfm of+ ErrorOnCodingFailure -> error e+ TransliterateCodingFailure -> YAndC replacementChar s+ {-# INLINE inputUnderflow #-}+ inputUnderflow =+ case cfm of+ ErrorOnCodingFailure ->+ error+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"+ TransliterateCodingFailure -> YAndC replacementChar D+ {-# INLINE_LATE step' #-}+ step' _ gst (OuterLoop st Nothing) = do+ r <- step (adaptState gst) st+ return $+ case r of+ Yield A.Array {..} s ->+ let p = unsafeForeignPtrToPtr aStart+ in Skip (InnerLoopDecodeInit s aStart p aEnd)+ Skip s -> Skip (OuterLoop s Nothing)+ Stop -> Skip D+ step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do+ r <- step (adaptState gst) st+ return $+ case r of+ Yield A.Array {..} s ->+ let p = unsafeForeignPtrToPtr aStart+ in Skip (InnerLoopDecoding s aStart p aEnd ds cp)+ Skip s -> Skip (OuterLoop s dst)+ Stop -> Skip inputUnderflow+ step' _ _ (InnerLoopDecodeInit st startf p end)+ | p == end = do+ liftIO $ touchForeignPtr startf+ return $ Skip $ OuterLoop st Nothing+ step' _ _ (InnerLoopDecodeInit st startf p end) = do+ x <- liftIO $ peek p+ -- Note: It is important to use a ">" instead of a "<=" test here for+ -- GHC to generate code layout for default branch prediction for the+ -- common case. This is fragile and might change with the compiler+ -- versions, we need a more reliable "likely" primitive to control+ -- branch predication.+ case x > 0x7f of+ False ->+ return $ Skip $ YAndC+ (unsafeChr (fromIntegral x))+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)+ -- Using a separate state here generates a jump to a separate code+ -- block in the core which seems to perform slightly better for the+ -- non-ascii case.+ True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x++ step' table _ (InnerLoopDecodeFirst st startf p end x) = do+ let (Tuple' sv cp) = decode0 table x+ return $+ case sv of+ 12 ->+ Skip $+ transliterateOrError+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)+ 0 -> error "unreachable state"+ _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)+ step' _ _ (InnerLoopDecoding st startf p end sv cp)+ | p == end = do+ liftIO $ touchForeignPtr startf+ return $ Skip $ OuterLoop st (Just (sv, cp))+ step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do+ x <- liftIO $ peek p+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x+ return $+ case sv of+ 0 ->+ Skip $+ YAndC+ (unsafeChr cp)+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)+ 12 ->+ Skip $+ transliterateOrError+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)+ _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)+ step' _ _ (YAndC c s) = return $ Yield c s+ step' _ _ D = return Stop++{-# INLINE decodeUtf8ArraysD #-}+decodeUtf8ArraysD ::+ MonadIO m+ => Stream m (A.Array Word8)+ -> Stream m Char+decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure++{-# INLINE decodeUtf8ArraysLenientD #-}+decodeUtf8ArraysLenientD ::+ MonadIO m+ => Stream m (A.Array Word8)+ -> Stream m Char+decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure++data EncodeState s = EncodeState s !WList++-- More yield points improve performance, but I am not sure if they can cause+-- too much code bloat or some trouble with fusion. So keeping only two yield+-- points for now, one for the ascii chars (fast path) and one for all other+-- paths (slow path).+{-# INLINE_NORMAL encodeUtf8D #-}+encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8+encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)+ where+ {-# INLINE_LATE step' #-}+ step' gst (EncodeState st WNil) = do+ r <- step (adaptState gst) st+ return $+ case r of+ Yield c s ->+ case ord c of+ x+ | x <= 0x7F ->+ Yield (fromIntegral x) (EncodeState s WNil)+ | x <= 0x7FF -> Skip (EncodeState s (ord2 c))+ | x <= 0xFFFF ->+ if isSurrogate c+ then error+ "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"+ else Skip (EncodeState s (ord3 c))+ | otherwise -> Skip (EncodeState s (ord4 c))+ Skip s -> Skip (EncodeState s WNil)+ Stop -> Stop+ step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)++ -- | Decode a stream of bytes to Unicode characters by mapping each byte to a -- corresponding Unicode 'Char' in 0-255 range. --@@ -79,8 +593,8 @@ convert c = let codepoint = ord c in if codepoint > 255- then error $ "Streamly.String.encodeLatin1 invalid \- \input char codepoint " ++ show codepoint+ then error $ "Streamly.String.encodeLatin1 invalid " +++ "input char codepoint " ++ show codepoint else fromIntegral codepoint -- | Like 'encodeLatin1' but silently truncates and maps input characters beyond@@ -98,14 +612,14 @@ -- /Since: 0.7.0/ {-# INLINE decodeUtf8 #-} decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char-decodeUtf8 = D.fromStreamD . D.decodeUtf8 . D.toStreamD+decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD -- | -- -- /Internal/ {-# INLINE decodeUtf8Arrays #-} decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char-decodeUtf8Arrays = D.fromStreamD . D.decodeUtf8Arrays . D.toStreamD+decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD -- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters. -- Any invalid codepoint encountered is replaced with the unicode replacement@@ -114,15 +628,15 @@ -- /Since: 0.7.0/ {-# INLINE decodeUtf8Lax #-} decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char-decodeUtf8Lax = D.fromStreamD . D.decodeUtf8Lenient . D.toStreamD+decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD -- | -- -- /Internal/ {-# INLINE decodeUtf8Either #-} decodeUtf8Either :: (Monad m, IsStream t)- => t m Word8 -> t m (Either D.DecodeError Char)-decodeUtf8Either = D.fromStreamD . D.decodeUtf8Either . D.toStreamD+ => t m Word8 -> t m (Either DecodeError Char)+decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD -- | --@@ -130,12 +644,12 @@ {-# INLINE resumeDecodeUtf8Either #-} resumeDecodeUtf8Either :: (Monad m, IsStream t)- => D.DecodeState- -> D.CodePoint+ => DecodeState+ -> CodePoint -> t m Word8- -> t m (Either D.DecodeError Char)+ -> t m (Either DecodeError Char) resumeDecodeUtf8Either st cp =- D.fromStreamD . D.resumeDecodeUtf8Either st cp . D.toStreamD+ D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD -- | --@@ -144,14 +658,14 @@ decodeUtf8ArraysLenient :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char decodeUtf8ArraysLenient =- D.fromStreamD . D.decodeUtf8ArraysLenient . D.toStreamD+ D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD -- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. -- -- /Since: 0.7.0/ {-# INLINE encodeUtf8 #-} encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8-encodeUtf8 = D.fromStreamD . D.encodeUtf8 . D.toStreamD+encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD {- -------------------------------------------------------------------------------
src/Streamly/Internal/FileSystem/Dir.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE MagicHash #-}@@ -73,8 +72,8 @@ -- import Streamly.Internal.Memory.Array.Types -- (Array(..), writeNUnsafe, defaultChunkSize, shrinkToFit, -- lpackArraysChunksOf)--- import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream)+-- import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) -- import qualified Streamly.Data.Fold as FL
src/Streamly/Internal/FileSystem/File.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}@@ -22,7 +21,7 @@ -- session consisting of multiple reads and writes to the handle, these APIs -- are one shot read or write APIs. These APIs open the file handle, perform -- the requested operation and close the handle. Thease are safer compared to--- the handle based APIs as there is no possiblity of a file descriptor+-- the handle based APIs as there is no possibility of a file descriptor -- leakage. -- -- > import qualified Streamly.Internal.FileSystem.File as File@@ -107,8 +106,8 @@ import Streamly.Internal.Data.Unfold.Types (Unfold(..)) import Streamly.Internal.Memory.Array.Types (Array(..), defaultChunkSize, writeNUnsafe)-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream) import Streamly.Internal.Data.SVar (MonadAsync) -- import Streamly.Data.Fold (Fold) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
src/Streamly/Internal/FileSystem/Handle.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}@@ -64,7 +63,8 @@ , fromChunks , putChunks , putStrings- -- , putLines+ , putBytes+ , putLines -- -- * Random Access (Seek) -- -- | Unlike the streaming APIs listed above, these APIs apply to devices or@@ -123,13 +123,12 @@ import Streamly.Internal.Memory.Array.Types (Array(..), writeNUnsafe, defaultChunkSize, shrinkToFit, lpackArraysChunksOf)-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream, mkStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) import qualified Streamly.Data.Fold as FL import qualified Streamly.Internal.Data.Fold.Types as FL-import qualified Streamly.Internal.Data.Unicode.Stream as U import qualified Streamly.Internal.Data.Unfold as UF import qualified Streamly.Internal.Memory.Array as IA import qualified Streamly.Internal.Memory.ArrayStream as AS@@ -346,6 +345,7 @@ -- Writing ------------------------------------------------------------------------------- +-- XXX use an unfold to fromObjects or fromUnfold so that we can put any object -- | Write a stream of arrays to a handle. -- -- @since 0.7.0@@ -362,13 +362,39 @@ putChunks :: (MonadIO m, Storable a) => SerialT m (Array a) -> m () putChunks = fromChunks stdout --- | Write a stream of strings to standard output using Latin1 encoding.+-- XXX use an unfold so that we can put any type of strings.+-- | Write a stream of strings to standard output using the supplied encoding.+-- Output is flushed to the device for each string. -- -- /Internal/ -- {-# INLINE putStrings #-}-putStrings :: MonadAsync m => SerialT m String -> m ()-putStrings = putChunks . S.mapM (IA.fromStream . U.encodeLatin1 . S.fromList)+putStrings :: MonadAsync m+ => (SerialT m Char -> SerialT m Word8) -> SerialT m String -> m ()+putStrings encode = putChunks . S.mapM (IA.fromStream . encode . S.fromList)++-- XXX use an unfold so that we can put lines from any object+-- | Write a stream of strings as separate lines to standard output using the+-- supplied encoding. Output is line buffered i.e. the output is written to the+-- device as soon as a newline is encountered.+--+-- /Internal/+--+{-# INLINE putLines #-}+putLines :: MonadAsync m+ => (SerialT m Char -> SerialT m Word8) -> SerialT m String -> m ()+putLines encode = putChunks . S.mapM+ (\xs -> IA.fromStream $ encode (S.fromList (xs ++ "\n")))++-- | Write a stream of bytes from standard output.+--+-- > putBytes = fromBytes stdout+--+-- /Internal/+--+{-# INLINE putBytes #-}+putBytes :: MonadIO m => SerialT m Word8 -> m ()+putBytes = fromBytes stdout -- | @fromChunksWithBufferOf bufsize handle stream@ writes a stream of arrays -- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@.
src/Streamly/Internal/Memory/Array.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-}@@ -57,6 +56,7 @@ -- Monadic APIs -- , newArray , A.writeN -- drop new+ , A.writeNAligned , A.write -- full buffer -- , writeLastN -- drop old (ring buffer) @@ -66,6 +66,7 @@ , toStream , toStreamRev , read+ , unsafeRead -- , readChunksOf -- * Random Access@@ -74,6 +75,7 @@ , last -- , (!!) , readIndex+ , A.unsafeIndex -- , readIndices -- , readRanges @@ -121,6 +123,9 @@ -- * Folding Arrays , streamFold , fold++ -- * Folds with Array as the container+ , D.lastN ) where @@ -133,18 +138,20 @@ import GHC.ForeignPtr (ForeignPtr(..)) import GHC.Ptr (Ptr(..))+import GHC.Prim (touch#)+import GHC.IO (IO(..)) import Streamly.Internal.Data.Fold.Types (Fold(..)) import Streamly.Internal.Data.Unfold.Types (Unfold(..)) import Streamly.Internal.Memory.Array.Types (Array(..), length)-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream) import qualified Streamly.Internal.Memory.Array.Types as A-import qualified Streamly.Streams.Prelude as P-import qualified Streamly.Streams.Serial as Serial-import qualified Streamly.Streams.StreamD as D-import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.Prelude as P+import qualified Streamly.Internal.Data.Stream.Serial as Serial+import qualified Streamly.Internal.Data.Stream.StreamD as D+import qualified Streamly.Internal.Data.Stream.StreamK as K ------------------------------------------------------------------------------- -- Construction@@ -236,6 +243,41 @@ let !x = A.unsafeInlineIO $ peek p return $ D.Yield x (ReadUState fp (p `plusPtr` (sizeOf (undefined :: a))))++-- | Unfold an array into a stream, does not check the end of the array, the+-- user is responsible for terminating the stream within the array bounds. For+-- high performance application where the end condition can be determined by+-- a terminating fold.+--+-- Written in the hope that it may be faster than "read", however, in the case+-- for which this was written, "read" proves to be faster even though the core+-- generated with unsafeRead looks simpler.+--+-- /Internal/+--+{-# INLINE_NORMAL unsafeRead #-}+unsafeRead :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a+unsafeRead = Unfold step inject+ where++ inject (Array fp _ _) = return fp++ {-# INLINE_LATE step #-}+ step (ForeignPtr p contents) = do+ -- unsafeInlineIO allows us to run this in Identity monad for pure+ -- toList/foldr case which makes them much faster due to not+ -- accumulating the list and fusing better with the pure consumers.+ --+ -- This should be safe as the array contents are guaranteed to be+ -- evaluated/written to before we peek at them.+ let !x = A.unsafeInlineIO $ do+ r <- peek (Ptr p)+ touch contents+ return r+ let !(Ptr p1) = Ptr p `plusPtr` (sizeOf (undefined :: a))+ return $ D.Yield x (ForeignPtr p1 contents)++ touch r = IO $ \s -> case touch# r s of s' -> (# s', () #) -- | > null arr = length arr == 0 --
src/Streamly/Internal/Memory/Array/Types.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExistentialQuantification #-}@@ -121,7 +120,7 @@ import qualified Streamly.Memory.Malloc as Malloc import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Streams.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamK as K import qualified GHC.Exts as Exts #ifdef DEVBUILD@@ -286,7 +285,7 @@ -- -- Internal routine for when the array is being created. Appends one item at -- the end of the array. Useful when sequentially writing a stream to the--- array. DOES NOT CHECK THE ARRAY BOUNDS.+-- array. {-# INLINE unsafeSnoc #-} unsafeSnoc :: forall a. Storable a => Array a -> a -> IO (Array a) unsafeSnoc arr@Array{..} x = do@@ -539,7 +538,7 @@ writeN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a) writeN = writeNAllocWith newArray --- | @writeNAligned n@ folds a maximum of @n@ elements from the input+-- | @writeNAligned alignment n@ folds a maximum of @n@ elements from the input -- stream to an 'Array' aligned to the given size. -- -- /Internal/
src/Streamly/Internal/Memory/ArrayStream.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-}@@ -50,14 +49,14 @@ import Prelude hiding (length, null, last, map, (!!), read, concat) import Streamly.Internal.Memory.Array.Types (Array(..), length)-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream) import qualified Streamly.Internal.Memory.Array as A import qualified Streamly.Internal.Memory.Array.Types as A import qualified Streamly.Internal.Prelude as S-import qualified Streamly.Streams.StreamD as D-import qualified Streamly.Streams.Prelude as P+import qualified Streamly.Internal.Data.Stream.StreamD as D+import qualified Streamly.Internal.Data.Stream.Prelude as P -- XXX efficiently compare two streams of arrays. Two streams can have chunks -- of different sizes, we can handle that in the stream comparison abstraction.
src/Streamly/Internal/Memory/Unicode/Array.hs view
@@ -1,4 +1,3 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE FlexibleContexts #-} -- |
+ src/Streamly/Internal/Mutable/Prim/Var.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE ScopedTypeVariables #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Mutable.Prim.Var+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- A mutable variable in a mutation capable monad (IO/ST) holding a 'Prim'+-- value. This allows fast modification because of unboxed storage.+--+-- = Multithread Consistency Notes+--+-- In general, any value that straddles a machine word cannot be guaranteed to+-- be consistently read from another thread without a lock. GHC heap objects+-- are always machine word aligned, therefore, a 'Var' is also word aligned. On+-- a 64-bit platform, writing a 64-bit aligned type from one thread and reading+-- it from another thread should give consistent old or new value. The same+-- holds true for 32-bit values on a 32-bit platform.++module Streamly.Internal.Mutable.Prim.Var+ (+ Var+ , MonadMut+ , Prim++ -- * Construction+ , newVar++ -- * Write+ , writeVar+ , modifyVar'++ -- * Read+ , readVar+ )+where++import Control.Monad.Primitive (PrimMonad(..), primitive_)+import Data.Primitive.Types (Prim, sizeOf#, readByteArray#, writeByteArray#)+import GHC.Exts (MutableByteArray#, newByteArray#)++-- | A 'Var' holds a single 'Prim' value.+data Var m a = Var (MutableByteArray# (PrimState m))++-- The name PrimMonad does not give a clue what it means, an explicit "Mut"+-- suffix provides a better hint. MonadMut is just a generalization of MonadIO.+--+-- | A monad that allows mutable operations using a state token.+type MonadMut = PrimMonad++-- | Create a new mutable variable.+{-# INLINE newVar #-}+newVar :: forall m a. (MonadMut m, Prim a) => a -> m (Var m a)+newVar x = primitive (\s# ->+ case newByteArray# (sizeOf# (undefined :: a)) s# of+ (# s1#, arr# #) ->+ case writeByteArray# arr# 0# x s1# of+ s2# -> (# s2#, Var arr# #)+ )++-- | Write a value to a mutable variable.+{-# INLINE writeVar #-}+writeVar :: (MonadMut m, Prim a) => Var m a -> a -> m ()+writeVar (Var arr#) x = primitive_ (writeByteArray# arr# 0# x)++-- | Read a value from a variable.+{-# INLINE readVar #-}+readVar :: (MonadMut m, Prim a) => Var m a -> m a+readVar (Var arr#) = primitive (readByteArray# arr# 0#)++-- | Modify the value of a mutable variable using a function with strict+-- application.+{-# INLINE modifyVar' #-}+modifyVar' :: (MonadMut m, Prim a) => Var m a -> (a -> a) -> m ()+modifyVar' (Var arr#) g = primitive_ $ \s# ->+ case readByteArray# arr# 0# s# of+ (# s'#, a #) -> let a' = g a in a' `seq` writeByteArray# arr# 0# a' s'#
src/Streamly/Internal/Network/Inet/TCP.hs view
@@ -1,10 +1,5 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} #include "inline.hs" @@ -24,11 +19,14 @@ -- * TCP Servers -- ** Unfolds acceptOnAddr+ , acceptOnAddrWith , acceptOnPort+ , acceptOnPortWith , acceptOnPortLocal -- ** Streams , connectionsOnAddr+ , connectionsOnAddrWith , connectionsOnPort , connectionsOnLocalHost @@ -71,6 +69,9 @@ -- , writeArray , writeChunks , fromChunks++ -- ** Transformation+ , transformBytesWith {- -- ** Sink Servers @@ -106,11 +107,12 @@ import Streamly (MonadAsync) import Streamly.Internal.Data.Fold.Types (Fold(..))+import Streamly.Internal.Data.SVar (fork) import Streamly.Internal.Data.Unfold.Types (Unfold(..)) import Streamly.Internal.Network.Socket (SockSpec(..), accept, connections)-import Streamly.Streams.Serial (SerialT)+import Streamly.Internal.Data.Stream.Serial (SerialT) import Streamly.Internal.Memory.Array.Types (Array(..), defaultChunkSize, writeNUnsafe)-import Streamly.Streams.StreamK.Type (IsStream)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream) import qualified Control.Monad.Catch as MC import qualified Network.Socket as Net@@ -127,16 +129,12 @@ -- Accept (unfolds) ------------------------------------------------------------------------------- --- | Unfold a tuple @(ipAddr, port)@ into a stream of connected TCP sockets.--- @ipAddr@ is the local IP address and @port@ is the local port on which--- connections are accepted.------ @since 0.7.0-{-# INLINE acceptOnAddr #-}-acceptOnAddr+{-# INLINE acceptOnAddrWith #-}+acceptOnAddrWith :: MonadIO m- => Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket-acceptOnAddr = UF.lmap f accept+ => [(SocketOption, Int)]+ -> Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket+acceptOnAddrWith opts = UF.lmap f accept where f (addr, port) = (maxListenQueue@@ -144,11 +142,28 @@ { sockFamily = AF_INET , sockType = Stream , sockProto = defaultProtocol -- TCP- , sockOpts = [(NoDelay,1), (ReuseAddr,1)]+ , sockOpts = opts } , SockAddrInet port (tupleToHostAddress addr) ) +-- | Unfold a tuple @(ipAddr, port)@ into a stream of connected TCP sockets.+-- @ipAddr@ is the local IP address and @port@ is the local port on which+-- connections are accepted.+--+-- @since 0.7.0+{-# INLINE acceptOnAddr #-}+acceptOnAddr+ :: MonadIO m+ => Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket+acceptOnAddr = acceptOnAddrWith []++{-# INLINE acceptOnPortWith #-}+acceptOnPortWith :: MonadIO m+ => [(SocketOption, Int)]+ -> Unfold m PortNumber Socket+acceptOnPortWith opts = UF.supplyFirst (acceptOnAddrWith opts) (0,0,0,0)+ -- | Like 'acceptOnAddr' but binds on the IPv4 address @0.0.0.0@ i.e. on all -- IPv4 addresses/interfaces of the machine and listens for TCP connections on -- the specified port.@@ -175,6 +190,22 @@ -- Accept (streams) ------------------------------------------------------------------------------- +{-# INLINE connectionsOnAddrWith #-}+connectionsOnAddrWith+ :: MonadAsync m+ => [(SocketOption, Int)]+ -> (Word8, Word8, Word8, Word8)+ -> PortNumber+ -> SerialT m Socket+connectionsOnAddrWith opts addr port =+ connections maxListenQueue SockSpec+ { sockFamily = AF_INET+ , sockType = Stream+ , sockProto = defaultProtocol+ , sockOpts = opts+ }+ (SockAddrInet port (tupleToHostAddress addr))+ -- | Like 'connections' but binds on the specified IPv4 address of the machine -- and listens for TCP connections on the specified port. --@@ -185,14 +216,7 @@ => (Word8, Word8, Word8, Word8) -> PortNumber -> SerialT m Socket-connectionsOnAddr addr port =- connections maxListenQueue SockSpec- { sockFamily = AF_INET- , sockType = Stream- , sockProto = defaultProtocol- , sockOpts = [(NoDelay,1), (ReuseAddr,1)]- }- (SockAddrInet port (tupleToHostAddress addr))+connectionsOnAddr = connectionsOnAddrWith [] -- | Like 'connections' but binds on the IPv4 address @0.0.0.0@ i.e. on all -- IPv4 addresses/interfaces of the machine and listens for TCP connections on@@ -331,13 +355,12 @@ where initial = do skt <- liftIO (connect addr port)- fld <- FL.initialize (SK.writeChunks skt)- `MC.onException` (liftIO $ Net.close skt)+ fld <- FL.initialize (SK.writeChunks skt) `MC.onException` liftIO (Net.close skt) return (fld, skt) step (fld, skt) x = do- r <- FL.runStep fld x `MC.onException` (liftIO $ Net.close skt)+ r <- FL.runStep fld x `MC.onException` liftIO (Net.close skt) return (r, skt)- extract ((Fold _ initial1 extract1), skt) = do+ extract (Fold _ initial1 extract1, skt) = do liftIO $ Net.close skt initial1 >>= extract1 @@ -386,3 +409,45 @@ write :: (MonadAsync m, MonadCatch m) => (Word8, Word8, Word8, Word8) -> PortNumber -> Fold m Word8 () write = writeWithBufferOf defaultChunkSize++-------------------------------------------------------------------------------+-- Transformations+-------------------------------------------------------------------------------++{-# INLINABLE withInputConnect #-}+withInputConnect+ :: (IsStream t, MonadCatch m, MonadAsync m)+ => (Word8, Word8, Word8, Word8)+ -> PortNumber+ -> SerialT m Word8+ -> (Socket -> t m a)+ -> t m a+withInputConnect addr port input f = S.bracket pre post handler++ where++ pre = do+ sk <- liftIO $ connect addr port+ tid <- fork (ISK.fromBytes sk input)+ return (sk, tid)++ handler (sk, _) = f sk++ -- XXX kill the thread immediately?+ post (sk, _) = liftIO $ Net.close sk++-- | Send an input stream to a remote host and produce the output stream from+-- the host. The server host just acts as a transformation function on the+-- input stream. Both sending and receiving happen asynchronously.+--+-- /Internal/+--+{-# INLINABLE transformBytesWith #-}+transformBytesWith+ :: (IsStream t, MonadAsync m, MonadCatch m)+ => (Word8, Word8, Word8, Word8)+ -> PortNumber+ -> SerialT m Word8+ -> t m Word8+transformBytesWith addr port input =+ withInputConnect addr port input ISK.toBytes
src/Streamly/Internal/Network/Socket.hs view
@@ -1,10 +1,6 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MagicHash #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} #include "inline.hs" @@ -21,8 +17,8 @@ ( SockSpec (..) -- * Use a socket- , useSocketM- , useSocket+ , handleWithM+ , handleWith -- * Accept connections , accept@@ -60,8 +56,9 @@ , fromBytes -- -- * Array Write- , writeArray+ , writeChunk , writeChunks+ , writeChunksWithBufferOf , writeStrings -- reading/writing datagrams@@ -78,11 +75,10 @@ import Foreign.Ptr (minusPtr, plusPtr, Ptr, castPtr) import Foreign.Storable (Storable(..)) import GHC.ForeignPtr (mallocPlainForeignPtrBytes)-import Network.Socket (sendBuf, recvBuf) import Network.Socket (Socket, SocketOption(..), Family(..), SockAddr(..), ProtocolNumber, withSocketsDo, SocketType(..), socket, bind,- setSocketOption)+ setSocketOption, sendBuf, recvBuf) #if MIN_VERSION_network(3,1,0) import Network.Socket (withFdSocket) #else@@ -94,14 +90,13 @@ import Streamly (MonadAsync) import Streamly.Internal.Data.Unfold.Types (Unfold(..))-import Streamly.Internal.Memory.Array.Types (Array(..))-import Streamly.Streams.Serial (SerialT)-import Streamly.Streams.StreamK.Type (IsStream, mkStream)+import Streamly.Internal.Memory.Array.Types (Array(..), lpackArraysChunksOf)+import Streamly.Internal.Data.Stream.Serial (SerialT)+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream) import Streamly.Data.Fold (Fold) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) import qualified Streamly.Data.Fold as FL-import qualified Streamly.Data.Unicode.Stream as U import qualified Streamly.Internal.Data.Fold.Types as FL import qualified Streamly.Internal.Data.Unfold as UF import qualified Streamly.Internal.Memory.Array as IA@@ -111,25 +106,25 @@ import qualified Streamly.Prelude as S import qualified Streamly.Internal.Data.Stream.StreamD.Type as D --- | @'useSocketM' socket act@ runs the monadic computation @act@ passing the--- socket handle to it. The handle will be closed on exit from 'useSocketM',+-- | @'handleWithM' socket act@ runs the monadic computation @act@ passing the+-- socket handle to it. The handle will be closed on exit from 'handleWithM', -- whether by normal termination or by raising an exception. If closing the -- handle raises an exception, then this exception will be raised by--- 'useSocketM' rather than any exception raised by 'act'.+-- 'handleWithM' rather than any exception raised by 'act'. -- -- @since 0.7.0-{-# INLINE useSocketM #-}-useSocketM :: (MonadMask m, MonadIO m) => Socket -> (Socket -> m ()) -> m ()-useSocketM sk f = finally (f sk) (liftIO (Net.close sk))+{-# INLINE handleWithM #-}+handleWithM :: (MonadMask m, MonadIO m) => (Socket -> m ()) -> Socket -> m ()+handleWithM f sk = finally (f sk) (liftIO (Net.close sk)) --- | Like 'useSocketM' but runs a streaming computation instead of a monadic+-- | Like 'handleWithM' but runs a streaming computation instead of a monadic -- computation. -- -- @since 0.7.0-{-# INLINE useSocket #-}-useSocket :: (IsStream t, MonadCatch m, MonadIO m)+{-# INLINE handleWith #-}+handleWith :: (IsStream t, MonadCatch m, MonadIO m) => Socket -> (Socket -> t m a) -> t m a-useSocket sk f = S.finally (liftIO $ Net.close sk) (f sk)+handleWith sk f = S.finally (liftIO $ Net.close sk) (f sk) ------------------------------------------------------------------------------- -- Accept (Unfolds)@@ -161,9 +156,7 @@ => Unfold m (Int, SockSpec, SockAddr) (Socket, SockAddr) listenTuples = Unfold step inject where- inject (listenQLen, spec, addr) = do- listener <- liftIO $ initListener listenQLen spec addr- return listener+ inject (listenQLen, spec, addr) = liftIO $ initListener listenQLen spec addr step listener = do r <- liftIO $ Net.accept listener@@ -209,8 +202,7 @@ -- /Internal/ {-# INLINE connections #-} connections :: MonadAsync m => Int -> SockSpec -> SockAddr -> SerialT m Socket-connections tcpListenQ spec addr = fmap fst $- recvConnectionTuplesWith tcpListenQ spec addr+connections tcpListenQ spec addr = fst <$> recvConnectionTuplesWith tcpListenQ spec addr ------------------------------------------------------------------------------- -- Array IO (Input)@@ -252,6 +244,8 @@ waitWhen0 0 s = when rtsSupportsBoundThreads $ #if MIN_VERSION_network(3,1,0) withFdSocket s $ \fd -> threadWaitWrite $ fromIntegral fd+#elif MIN_VERSION_network(3,0,0)+ fdSocket s >>= threadWaitWrite . fromIntegral #else let fd = fdSocket s in threadWaitWrite $ fromIntegral fd #endif@@ -282,19 +276,19 @@ -- | Write an Array to a file handle. -- -- @since 0.7.0-{-# INLINABLE writeArray #-}-writeArray :: Storable a => Socket -> Array a -> IO ()-writeArray = writeArrayWith sendAll+{-# INLINABLE writeChunk #-}+writeChunk :: Storable a => Socket -> Array a -> IO ()+writeChunk = writeArrayWith sendAll ------------------------------------------------------------------------------- -- Stream of Arrays IO ------------------------------------------------------------------------------- -{-# INLINABLE readChunksUptoWith #-}-readChunksUptoWith :: (IsStream t, MonadIO m)+{-# INLINABLE _readChunksUptoWith #-}+_readChunksUptoWith :: (IsStream t, MonadIO m) => (Int -> h -> IO (Array Word8)) -> Int -> h -> t m (Array Word8)-readChunksUptoWith f size h = go+_readChunksUptoWith f size h = go where -- XXX use cons/nil instead go = mkStream $ \_ yld _ stp -> do@@ -307,10 +301,19 @@ -- The maximum size of a single array is limited to @size@. -- 'fromHandleArraysUpto' ignores the prevailing 'TextEncoding' and 'NewlineMode' -- on the 'Handle'.-{-# INLINABLE toChunksWithBufferOf #-}+{-# INLINE_NORMAL toChunksWithBufferOf #-} toChunksWithBufferOf :: (IsStream t, MonadIO m) => Int -> Socket -> t m (Array Word8)-toChunksWithBufferOf = readChunksUptoWith readArrayOf+-- toChunksWithBufferOf = _readChunksUptoWith readArrayOf+toChunksWithBufferOf size h = D.fromStreamD (D.Stream step ())+ where+ {-# INLINE_LATE step #-}+ step _ _ = do+ arr <- liftIO $ readArrayOf size h+ return $+ case A.length arr of+ 0 -> D.Stop+ _ -> D.Yield arr () -- XXX read 'Array a' instead of Word8 --@@ -408,7 +411,7 @@ {-# INLINE fromChunks #-} fromChunks :: (MonadIO m, Storable a) => Socket -> SerialT m (Array a) -> m ()-fromChunks h m = S.mapM_ (liftIO . writeArray h) m+fromChunks h = S.mapM_ (liftIO . writeChunk h) -- | Write a stream of arrays to a socket. Each array in the stream is written -- to the socket as a separate IO request.@@ -416,16 +419,30 @@ -- @since 0.7.0 {-# INLINE writeChunks #-} writeChunks :: (MonadIO m, Storable a) => Socket -> Fold m (Array a) ()-writeChunks h = FL.drainBy (liftIO . writeArray h)+writeChunks h = FL.drainBy (liftIO . writeChunk h) --- | Write a stream of strings to a socket in Latin1 encoding.+-- | @writeChunksWithBufferOf bufsize socket@ writes a stream of arrays+-- to @socket@ after coalescing the adjacent arrays in chunks of @bufsize@.+-- We never split an array, if a single array is bigger than the specified size+-- it emitted as it is. Multiple arrays are coalesed as long as the total size+-- remains below the specified size. --+-- @since 0.7.0+{-# INLINE writeChunksWithBufferOf #-}+writeChunksWithBufferOf :: (MonadIO m, Storable a)+ => Int -> Socket -> Fold m (Array a) ()+writeChunksWithBufferOf n h = lpackArraysChunksOf n (writeChunks h)++-- | Write a stream of strings to a socket in Latin1 encoding. Output is+-- flushed to the socket for each string.+-- -- /Internal/ -- {-# INLINE writeStrings #-}-writeStrings :: MonadIO m => Socket -> Fold m String ()-writeStrings h =- FL.lmapM (IA.fromStream . U.encodeLatin1 . S.fromList) (writeChunks h)+writeStrings :: MonadIO m+ => (SerialT m Char -> SerialT m Word8) -> Socket -> Fold m String ()+writeStrings encode h =+ FL.lmapM (IA.fromStream . encode . S.fromList) (writeChunks h) -- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes. --
src/Streamly/Internal/Prelude.hs view
@@ -1,3700 +1,4401 @@-{-# OPTIONS_HADDOCK hide #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE FlexibleContexts #-}--#if __GLASGOW_HASKELL__ >= 800-{-# OPTIONS_GHC -Wno-orphans #-}-#endif--#include "../Streams/inline.hs"---- |--- Module : Streamly.Internal.Prelude--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-----module Streamly.Internal.Prelude- (- -- * Construction- -- ** Primitives- K.nil- , K.nilM- , K.cons- , (K..:)-- , consM- , (|:)-- -- ** From Values- , yield- , yieldM- , repeat- , repeatM- , replicate- , replicateM-- -- ** Enumeration- , Enumerable (..)- , enumerate- , enumerateTo-- -- ** From Generators- , unfoldr- , unfoldrM- , unfold- , iterate- , iterateM- , fromIndices- , fromIndicesM-- -- ** From Containers- , P.fromList- , fromListM- , K.fromFoldable- , fromFoldableM-- -- * Elimination-- -- ** Deconstruction- , uncons- , tail- , init-- -- ** Folding- -- ** Right Folds- , foldrM- , foldrS- , foldrT- , foldr-- -- ** Left Folds- , foldl'- , foldl1'- , foldlM'-- -- ** Full Folds-- -- -- ** To Summary (Full Folds)- , drain- , last- , length- , sum- , product- --, mconcat-- -- -- ** To Summary (Maybe) (Full Folds)- , maximumBy- , maximum- , minimumBy- , minimum- , the-- -- ** Partial Folds-- -- -- ** To Elements (Partial Folds)- , drainN- , drainWhile-- -- -- | Folds that extract selected elements of a stream or their properties.- , (!!)- , head- , findM- , find- , lookup- , findIndex- , elemIndex-- -- -- ** To Boolean (Partial Folds)- , null- , elem- , notElem- , all- , any- , and- , or-- -- ** To Containers- , toList- , toListRev- , toPure- , toPureRev-- -- ** Composable Left Folds- , fold-- , toStream -- XXX rename to write?- , toStreamRev -- XXX rename to writeRev?-- -- * Transformation- , transform-- -- ** Mapping- , Serial.map- , sequence- , mapM- , mapM_-- -- ** Scanning- -- ** Left scans- , scanl'- , scanlM'- , postscanl'- , postscanlM'- , prescanl'- , prescanlM'- , scanl1'- , scanl1M'-- -- ** Scan Using Fold- , scan- , postscan-- -- , lscanl'- -- , lscanlM'- -- , lscanl1'- -- , lscanl1M'- --- -- , lpostscanl'- -- , lpostscanlM'- -- , lprescanl'- -- , lprescanlM'-- -- ** Indexing- , indexed- , indexedR- -- , timestamped- -- , timestampedR -- timer-- -- ** Filtering-- , filter- , filterM-- -- ** Stateful Filters- , take- -- , takeEnd- , takeWhile- , takeWhileM- -- , takeWhileEnd- , drop- -- , dropEnd- , dropWhile- , dropWhileM- -- , dropWhileEnd- -- , dropAround- , deleteBy- , uniq- -- , uniqBy -- by predicate e.g. to remove duplicate "/" in a path- -- , uniqOn -- to remove duplicate sequences- -- , pruneBy -- dropAround + uniqBy - like words-- -- ** Mapping Filters- , mapMaybe- , mapMaybeM-- -- ** Scanning Filters- , findIndices- , elemIndices- -- , seqIndices -- search a sequence in the stream-- -- ** Insertion- , insertBy- , intersperseM- , intersperse- , intersperseSuffix- -- , intersperseBySpan- , interjectSuffix-- -- ** Reordering- , reverse- , reverse'-- -- * Multi-Stream Operations-- -- ** Appending- , append-- -- ** Interleaving- , interleave- , interleaveMin- , interleaveSuffix- , interleaveInfix-- , Serial.wSerialFst- , Serial.wSerialMin-- -- ** Scheduling- , roundrobin-- -- ** Parallel- , Par.parallelFst- , Par.parallelMin-- -- ** Merging-- -- , merge- , mergeBy- , mergeByM- , mergeAsyncBy- , mergeAsyncByM-- -- ** Zipping- , zipWith- , zipWithM- , Z.zipAsyncWith- , Z.zipAsyncWithM-- -- ** Nested Streams- , concatMapM- , concatUnfold- , concatUnfoldInterleave- , concatUnfoldRoundrobin- , concatMap- , concatMapWith- , gintercalate- , gintercalateSuffix- , intercalate- , intercalateSuffix- , interpose- , interposeSuffix-- -- -- ** Breaking-- -- By chunks- , splitAt -- spanN- -- , splitIn -- sessionN-- -- By elements- , span -- spanWhile- , break -- breakBefore- -- , breakAfter- -- , breakOn- -- , breakAround- , spanBy- , spanByRolling-- -- By sequences- -- , breakOnSeq-- -- ** Splitting- -- , groupScan-- -- -- *** Chunks- , chunksOf- , chunksOf2- , arraysOf- , intervalsOf-- -- -- *** Using Element Separators- , splitOn- , splitOnSuffix- -- , splitOnPrefix-- -- , splitBy- , splitWithSuffix- -- , splitByPrefix- , wordsBy -- stripAndCompactBy-- -- -- *** Using Sequence Separators- , splitOnSeq- , splitOnSuffixSeq- -- , splitOnPrefixSeq-- -- Keeping the delimiters- , splitBySeq- , splitWithSuffixSeq- -- , splitByPrefixSeq- -- , wordsBySeq-- -- Splitting using multiple sequence separators- -- , splitOnAnySeq- -- , splitOnAnySuffixSeq- -- , splitOnAnyPrefixSeq-- -- Nested splitting- , splitInnerBy- , splitInnerBySuffix-- -- ** Grouping- , groups- , groupsBy- , groupsByRolling-- -- ** Distributing- , trace- , tap- , Par.tapAsync-- -- * Windowed Classification-- -- ** Tumbling Windows- -- , classifyChunksOf- , classifySessionsBy- , classifySessionsOf-- -- ** Keep Alive Windows- -- , classifyKeepAliveChunks- , classifyKeepAliveSessions-- {-- -- ** Sliding Windows- , classifySlidingChunks- , classifySlidingSessions- -}- -- ** Sliding Window Buffers- -- , slidingChunkBuffer- -- , slidingSessionBuffer-- -- ** Containers of Streams- , foldWith- , foldMapWith- , forEachWith-- -- ** Folding- , eqBy- , cmpBy- , isPrefixOf- -- , isSuffixOf- -- , isInfixOf- , isSubsequenceOf- , stripPrefix- -- , stripSuffix- -- , stripInfix-- -- * Exceptions- , before- , after- , bracket- , onException- , finally- , handle-- -- * Generalize Inner Monad- , hoist- , generally-- -- * Transform Inner Monad- , liftInner- , runReaderT- , evalStateT- , usingStateT- , runStateT-- -- * Diagnostics- , inspectMode-- -- * Deprecated- , K.once- , each- , scanx- , foldx- , foldxM- , foldr1- , runStream- , runN- , runWhile- , fromHandle- , toHandle- )-where--import Control.Concurrent (threadDelay)-import Control.Exception (Exception)-import Control.Monad (void)-import Control.Monad.Catch (MonadCatch)-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader (ReaderT)-import Control.Monad.State.Strict (StateT)-import Control.Monad.Trans (MonadTrans(..))-import Data.Functor.Identity (Identity (..))-import Data.Heap (Entry(..))-import Data.Maybe (isJust, fromJust, isNothing)-import Foreign.Storable (Storable)-import Prelude- hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,- foldl, map, mapM, mapM_, sequence, all, any, sum, product, elem,- notElem, maximum, minimum, head, last, tail, length, null,- reverse, iterate, init, and, or, lookup, foldr1, (!!),- scanl, scanl1, replicate, concatMap, span, splitAt, break,- repeat)--import qualified Data.Heap as H-import qualified Data.Map.Strict as Map-import qualified Prelude-import qualified System.IO as IO--import Streamly.Streams.Enumeration (Enumerable(..), enumerate, enumerateTo)-import Streamly.Internal.Data.Fold.Types (Fold (..), Fold2 (..))-import Streamly.Internal.Data.Unfold.Types (Unfold)-import Streamly.Internal.Memory.Array.Types (Array, writeNUnsafe)--- import Streamly.Memory.Ring (Ring)-import Streamly.Internal.Data.SVar (MonadAsync, defState)-import Streamly.Streams.Async (mkAsync')-import Streamly.Streams.Combinators (inspectMode, maxYields)-import Streamly.Streams.Prelude- (fromStreamS, toStreamS, foldWith, foldMapWith, forEachWith)-import Streamly.Streams.StreamD (fromStreamD, toStreamD)-import Streamly.Streams.StreamK (IsStream((|:), consM))-import Streamly.Streams.Serial (SerialT)-import Streamly.Internal.Data.Pipe.Types (Pipe (..))-import Streamly.Internal.Data.Time.Units- (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime,- toAbsTime)--import Streamly.Internal.Data.Strict--import qualified Streamly.Internal.Memory.Array as A-import qualified Streamly.Data.Fold as FL-import qualified Streamly.Internal.Data.Fold.Types as FL-import qualified Streamly.Streams.Prelude as P-import qualified Streamly.Streams.StreamK as K-import qualified Streamly.Streams.StreamD as D-import qualified Streamly.Streams.Zip as Z--#ifdef USE_STREAMK_ONLY-import qualified Streamly.Streams.StreamK as S-import qualified Streamly.Streams.Zip as S-#else-import qualified Streamly.Streams.StreamD as S-#endif--import qualified Streamly.Streams.Serial as Serial-import qualified Streamly.Streams.Parallel as Par----------------------------------------------------------------------------------- Deconstruction----------------------------------------------------------------------------------- | Decompose a stream into its head and tail. If the stream is empty, returns--- 'Nothing'. If the stream is non-empty, returns @Just (a, ma)@, where @a@ is--- the head of the stream and @ma@ its tail.------ This is a brute force primitive. Avoid using it as long as possible, use it--- when no other combinator can do the job. This can be used to do pretty much--- anything in an imperative manner, as it just breaks down the stream into--- individual elements and we can loop over them as we deem fit. For example,--- this can be used to convert a streamly stream into other stream types.------ @since 0.1.0-{-# INLINE uncons #-}-uncons :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (a, t m a))-uncons m = K.uncons (K.adapt m)----------------------------------------------------------------------------------- Generation by Unfolding----------------------------------------------------------------------------------- |--- @--- unfoldr step s =--- case step s of--- Nothing -> 'K.nil'--- Just (a, b) -> a \`cons` unfoldr step b--- @------ Build a stream by unfolding a /pure/ step function @step@ starting from a--- seed @s@. The step function returns the next element in the stream and the--- next seed value. When it is done it returns 'Nothing' and the stream ends.--- For example,------ @--- let f b =--- if b > 3--- then Nothing--- else Just (b, b + 1)--- in toList $ unfoldr f 0--- @--- @--- [0,1,2,3]--- @------ @since 0.1.0-{-# INLINE_EARLY unfoldr #-}-unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a-unfoldr step seed = fromStreamS (S.unfoldr step seed)-{-# RULES "unfoldr fallback to StreamK" [1]- forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}---- | Build a stream by unfolding a /monadic/ step function starting from a--- seed. The step function returns the next element in the stream and the next--- seed value. When it is done it returns 'Nothing' and the stream ends. For--- example,------ @--- let f b =--- if b > 3--- then return Nothing--- else print b >> return (Just (b, b + 1))--- in drain $ unfoldrM f 0--- @--- @--- 0--- 1--- 2--- 3--- @--- When run concurrently, the next unfold step can run concurrently with the--- processing of the output of the previous step. Note that more than one step--- cannot run concurrently as the next step depends on the output of the--- previous step.------ @--- (asyncly $ S.unfoldrM (\\n -> liftIO (threadDelay 1000000) >> return (Just (n, n + 1))) 0)--- & S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()--- @------ /Concurrent/------ /Since: 0.1.0/-{-# INLINE_EARLY unfoldrM #-}-unfoldrM :: (IsStream t, MonadAsync m) => (b -> m (Maybe (a, b))) -> b -> t m a-unfoldrM = K.unfoldrM--{-# RULES "unfoldrM serial" unfoldrM = unfoldrMSerial #-}-{-# INLINE_EARLY unfoldrMSerial #-}-unfoldrMSerial :: MonadAsync m => (b -> m (Maybe (a, b))) -> b -> SerialT m a-unfoldrMSerial step seed = fromStreamS (S.unfoldrM step seed)---- | Convert an 'Unfold' into a stream by supplying it an input seed.------ >>> unfold UF.replicateM 10 (putStrLn "hello")------ /Since: 0.7.0/-{-# INLINE unfold #-}-unfold :: (IsStream t, Monad m) => Unfold m a b -> a -> t m b-unfold unf x = fromStreamD $ D.unfold unf x----------------------------------------------------------------------------------- Specialized Generation----------------------------------------------------------------------------------- Faster than yieldM because there is no bind.------ |--- @--- yield a = a \`cons` nil--- @------ Create a singleton stream from a pure value.------ The following holds in monadic streams, but not in Zip streams:------ @--- yield = pure--- yield = yieldM . pure--- @------ In Zip applicative streams 'yield' is not the same as 'pure' because in that--- case 'pure' is equivalent to 'repeat' instead. 'yield' and 'pure' are--- equally efficient, in other cases 'yield' may be slightly more efficient--- than the other equivalent definitions.------ @since 0.4.0-{-# INLINE yield #-}-yield :: IsStream t => a -> t m a-yield = K.yield---- |--- @--- yieldM m = m \`consM` nil--- @------ Create a singleton stream from a monadic action.------ @--- > toList $ yieldM getLine--- hello--- ["hello"]--- @------ @since 0.4.0-{-# INLINE yieldM #-}-yieldM :: (Monad m, IsStream t) => m a -> t m a-yieldM = K.yieldM---- |--- @--- fromIndices f = let g i = f i \`cons` g (i + 1) in g 0--- @------ Generate an infinite stream, whose values are the output of a function @f@--- applied on the corresponding index. Index starts at 0.------ @--- > S.toList $ S.take 5 $ S.fromIndices id--- [0,1,2,3,4]--- @------ @since 0.6.0-{-# INLINE fromIndices #-}-fromIndices :: (IsStream t, Monad m) => (Int -> a) -> t m a-fromIndices = fromStreamS . S.fromIndices------- |--- @--- fromIndicesM f = let g i = f i \`consM` g (i + 1) in g 0--- @------ Generate an infinite stream, whose values are the output of a monadic--- function @f@ applied on the corresponding index. Index starts at 0.------ /Concurrent/------ @since 0.6.0-{-# INLINE_EARLY fromIndicesM #-}-fromIndicesM :: (IsStream t, MonadAsync m) => (Int -> m a) -> t m a-fromIndicesM = K.fromIndicesM--{-# RULES "fromIndicesM serial" fromIndicesM = fromIndicesMSerial #-}-{-# INLINE fromIndicesMSerial #-}-fromIndicesMSerial :: MonadAsync m => (Int -> m a) -> SerialT m a-fromIndicesMSerial = fromStreamS . S.fromIndicesM---- |--- @--- replicateM = take n . repeatM--- @------ Generate a stream by performing a monadic action @n@ times. Same as:------ @--- drain $ serially $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)--- drain $ asyncly $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)--- @------ /Concurrent/------ @since 0.1.1-{-# INLINE_EARLY replicateM #-}-replicateM :: (IsStream t, MonadAsync m) => Int -> m a -> t m a-replicateM = K.replicateM--{-# RULES "replicateM serial" replicateM = replicateMSerial #-}-{-# INLINE replicateMSerial #-}-replicateMSerial :: MonadAsync m => Int -> m a -> SerialT m a-replicateMSerial n = fromStreamS . S.replicateM n---- |--- @--- replicate = take n . repeat--- @------ Generate a stream of length @n@ by repeating a value @n@ times.------ @since 0.6.0-{-# INLINE_NORMAL replicate #-}-replicate :: (IsStream t, Monad m) => Int -> a -> t m a-replicate n = fromStreamS . S.replicate n---- |--- Generate an infinite stream by repeating a pure value.------ @since 0.4.0-{-# INLINE_NORMAL repeat #-}-repeat :: (IsStream t, Monad m) => a -> t m a-repeat = fromStreamS . S.repeat---- |--- @--- repeatM = fix . consM--- repeatM = cycle1 . yieldM--- @------ Generate a stream by repeatedly executing a monadic action forever.------ @--- drain $ serially $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)--- drain $ asyncly $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)--- @------ /Concurrent, infinite (do not use with 'parallely')/------ @since 0.2.0-{-# INLINE_EARLY repeatM #-}-repeatM :: (IsStream t, MonadAsync m) => m a -> t m a-repeatM = K.repeatM--{-# RULES "repeatM serial" repeatM = repeatMSerial #-}-{-# INLINE repeatMSerial #-}-repeatMSerial :: MonadAsync m => m a -> SerialT m a-repeatMSerial = fromStreamS . S.repeatM---- |--- @--- iterate f x = x \`cons` iterate f x--- @------ Generate an infinite stream with @x@ as the first element and each--- successive element derived by applying the function @f@ on the previous--- element.------ @--- > S.toList $ S.take 5 $ S.iterate (+1) 1--- [1,2,3,4,5]--- @------ @since 0.1.2-iterate :: IsStream t => (a -> a) -> a -> t m a-iterate step = K.fromStream . go- where- go s = K.cons s (go (step s))---- |--- @--- iterateM f m = m >>= \a -> return a \`consM` iterateM f (f a)--- @------ Generate an infinite stream with the first element generated by the action--- @m@ and each successive element derived by applying the monadic function--- @f@ on the previous element.------ When run concurrently, the next iteration can run concurrently with the--- processing of the previous iteration. Note that more than one iteration--- cannot run concurrently as the next iteration depends on the output of the--- previous iteration.------ @--- drain $ serially $ S.take 10 $ S.iterateM--- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) (return 0)------ drain $ asyncly $ S.take 10 $ S.iterateM--- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) (return 0)--- @------ /Concurrent/------ /Since: 0.7.0 (signature change)/------ /Since: 0.1.2/-iterateM :: (IsStream t, MonadAsync m) => (a -> m a) -> m a -> t m a-iterateM step = go- where- go s = K.mkStream $ \st stp sng yld -> do- next <- s- K.foldStreamShared st stp sng yld (return next |: go (step next))----------------------------------------------------------------------------------- Conversions----------------------------------------------------------------------------------- |--- @--- fromListM = 'Prelude.foldr' 'K.consM' 'K.nil'--- @------ Construct a stream from a list of monadic actions. This is more efficient--- than 'fromFoldableM' for serial streams.------ @since 0.4.0-{-# INLINE_EARLY fromListM #-}-fromListM :: (MonadAsync m, IsStream t) => [m a] -> t m a-fromListM = fromStreamD . D.fromListM-{-# RULES "fromListM fallback to StreamK" [1]- forall a. D.toStreamK (D.fromListM a) = fromFoldableM a #-}---- |--- @--- fromFoldableM = 'Prelude.foldr' 'consM' 'K.nil'--- @------ Construct a stream from a 'Foldable' containing monadic actions.------ @--- drain $ serially $ S.fromFoldableM $ replicateM 10 (threadDelay 1000000 >> print 1)--- drain $ asyncly $ S.fromFoldableM $ replicateM 10 (threadDelay 1000000 >> print 1)--- @------ /Concurrent (do not use with 'parallely' on infinite containers)/------ @since 0.3.0-{-# INLINE fromFoldableM #-}-fromFoldableM :: (IsStream t, MonadAsync m, Foldable f) => f (m a) -> t m a-fromFoldableM = Prelude.foldr consM K.nil---- | Same as 'fromFoldable'.------ @since 0.1.0-{-# DEPRECATED each "Please use fromFoldable instead." #-}-{-# INLINE each #-}-each :: (IsStream t, Foldable f) => f a -> t m a-each = K.fromFoldable---- | Read lines from an IO Handle into a stream of Strings.------ @since 0.1.0-{-# DEPRECATED fromHandle- "Please use Streamly.FileSystem.Handle module (see the changelog)" #-}-fromHandle :: (IsStream t, MonadIO m) => IO.Handle -> t m String-fromHandle h = go- where- go = K.mkStream $ \_ yld _ stp -> do- eof <- liftIO $ IO.hIsEOF h- if eof- then stp- else do- str <- liftIO $ IO.hGetLine h- yld str go----------------------------------------------------------------------------------- Elimination by Folding----------------------------------------------------------------------------------- | Right associative/lazy pull fold. @foldrM build final stream@ constructs--- an output structure using the step function @build@. @build@ is invoked with--- the next input element and the remaining (lazy) tail of the output--- structure. It builds a lazy output expression using the two. When the "tail--- structure" in the output expression is evaluated it calls @build@ again thus--- lazily consuming the input @stream@ until either the output expression built--- by @build@ is free of the "tail" or the input is exhausted in which case--- @final@ is used as the terminating case for the output structure. For more--- details see the description in the previous section.------ Example, determine if any element is 'odd' in a stream:------ >>> S.foldrM (\x xs -> if odd x then return True else xs) (return False) $ S.fromList (2:4:5:undefined)--- > True------ /Since: 0.7.0 (signature changed)/------ /Since: 0.2.0 (signature changed)/------ /Since: 0.1.0/-{-# INLINE foldrM #-}-foldrM :: Monad m => (a -> m b -> m b) -> m b -> SerialT m a -> m b-foldrM = P.foldrM---- | Right fold to a streaming monad.------ > foldrS S.cons S.nil === id------ 'foldrS' can be used to perform stateless stream to stream transformations--- like map and filter in general. It can be coupled with a scan to perform--- stateful transformations. However, note that the custom map and filter--- routines can be much more efficient than this due to better stream fusion.------ >>> S.toList $ S.foldrS S.cons S.nil $ S.fromList [1..5]--- > [1,2,3,4,5]------ Find if any element in the stream is 'True':------ >>> S.toList $ S.foldrS (\x xs -> if odd x then return True else xs) (return False) $ (S.fromList (2:4:5:undefined) :: SerialT IO Int)--- > [True]------ Map (+2) on odd elements and filter out the even elements:------ >>> S.toList $ S.foldrS (\x xs -> if odd x then (x + 2) `S.cons` xs else xs) S.nil $ (S.fromList [1..5] :: SerialT IO Int)--- > [3,5,7]------ 'foldrM' can also be represented in terms of 'foldrS', however, the former--- is much more efficient:------ > foldrM f z s = runIdentityT $ foldrS (\x xs -> lift $ f x (runIdentityT xs)) (lift z) s------ @since 0.7.0-{-# INLINE foldrS #-}-foldrS :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrS = K.foldrS---- | Right fold to a transformer monad. This is the most general right fold--- function. 'foldrS' is a special case of 'foldrT', however 'foldrS'--- implementation can be more efficient:------ > foldrS = foldrT--- > foldrM f z s = runIdentityT $ foldrT (\x xs -> lift $ f x (runIdentityT xs)) (lift z) s------ 'foldrT' can be used to translate streamly streams to other transformer--- monads e.g. to a different streaming type.------ @since 0.7.0-{-# INLINE foldrT #-}-foldrT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)- => (a -> s m b -> s m b) -> s m b -> t m a -> s m b-foldrT f z s = S.foldrT f z (toStreamS s)---- | Right fold, lazy for lazy monads and pure streams, and strict for strict--- monads.------ Please avoid using this routine in strict monads like IO unless you need a--- strict right fold. This is provided only for use in lazy monads (e.g.--- Identity) or pure streams. Note that with this signature it is not possible--- to implement a lazy foldr when the monad @m@ is strict. In that case it--- would be strict in its accumulator and therefore would necessarily consume--- all its input.------ @since 0.1.0-{-# INLINE foldr #-}-foldr :: Monad m => (a -> b -> b) -> b -> SerialT m a -> m b-foldr = P.foldr---- XXX This seems to be of limited use as it cannot be used to construct--- recursive structures and for reduction foldl1' is better.------ | Lazy right fold for non-empty streams, using first element as the starting--- value. Returns 'Nothing' if the stream is empty.------ @since 0.5.0-{-# INLINE foldr1 #-}-{-# DEPRECATED foldr1 "Use foldrM instead." #-}-foldr1 :: Monad m => (a -> a -> a) -> SerialT m a -> m (Maybe a)-foldr1 f m = S.foldr1 f (toStreamS m)---- | Strict left fold with an extraction function. Like the standard strict--- left fold, but applies a user supplied extraction function (the third--- argument) to the folded value at the end. This is designed to work with the--- @foldl@ library. The suffix @x@ is a mnemonic for extraction.------ @since 0.2.0-{-# DEPRECATED foldx "Please use foldl' followed by fmap instead." #-}-{-# INLINE foldx #-}-foldx :: Monad m => (x -> a -> x) -> x -> (x -> b) -> SerialT m a -> m b-foldx = P.foldlx'---- | Left associative/strict push fold. @foldl' reduce initial stream@ invokes--- @reduce@ with the accumulator and the next input in the input stream, using--- @initial@ as the initial value of the current value of the accumulator. When--- the input is exhausted the current value of the accumulator is returned.--- Make sure to use a strict data structure for accumulator to not build--- unnecessary lazy expressions unless that's what you want. See the previous--- section for more details.------ @since 0.2.0-{-# INLINE foldl' #-}-foldl' :: Monad m => (b -> a -> b) -> b -> SerialT m a -> m b-foldl' = P.foldl'---- | Strict left fold, for non-empty streams, using first element as the--- starting value. Returns 'Nothing' if the stream is empty.------ @since 0.5.0-{-# INLINE foldl1' #-}-foldl1' :: Monad m => (a -> a -> a) -> SerialT m a -> m (Maybe a)-foldl1' step m = do- r <- uncons m- case r of- Nothing -> return Nothing- Just (h, t) -> do- res <- foldl' step h t- return $ Just res---- | Like 'foldx', but with a monadic step function.------ @since 0.2.0-{-# DEPRECATED foldxM "Please use foldlM' followed by fmap instead." #-}-{-# INLINE foldxM #-}-foldxM :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> SerialT m a -> m b-foldxM = P.foldlMx'---- | Like 'foldl'' but with a monadic step function.------ @since 0.2.0-{-# INLINE foldlM' #-}-foldlM' :: Monad m => (b -> a -> m b) -> b -> SerialT m a -> m b-foldlM' step begin m = S.foldlM' step begin $ toStreamS m----------------------------------------------------------------------------------- Running a Fold----------------------------------------------------------------------------------- | Fold a stream using the supplied left fold.------ >>> S.fold FL.sum (S.enumerateFromTo 1 100)--- 5050------ @since 0.7.0-{-# INLINE fold #-}-fold :: Monad m => Fold m a b -> SerialT m a -> m b-fold = P.runFold----------------------------------------------------------------------------------- Running a sink---------------------------------------------------------------------------------{---- | Drain a stream to a 'Sink'.-{-# INLINE runSink #-}-runSink :: Monad m => Sink m a -> SerialT m a -> m ()-runSink = fold . toFold--}----------------------------------------------------------------------------------- Specialized folds----------------------------------------------------------------------------------- |--- > drain = mapM_ (\_ -> return ())------ Run a stream, discarding the results. By default it interprets the stream--- as 'SerialT', to run other types of streams use the type adapting--- combinators for example @drain . 'asyncly'@.------ @since 0.7.0-{-# INLINE drain #-}-drain :: Monad m => SerialT m a -> m ()-drain = P.drain---- | Run a stream, discarding the results. By default it interprets the stream--- as 'SerialT', to run other types of streams use the type adapting--- combinators for example @runStream . 'asyncly'@.------ @since 0.2.0-{-# DEPRECATED runStream "Please use \"drain\" instead" #-}-{-# INLINE runStream #-}-runStream :: Monad m => SerialT m a -> m ()-runStream = drain---- |--- > drainN n = drain . take n------ Run maximum up to @n@ iterations of a stream.------ @since 0.7.0-{-# INLINE drainN #-}-drainN :: Monad m => Int -> SerialT m a -> m ()-drainN n = drain . take n---- |--- > runN n = runStream . take n------ Run maximum up to @n@ iterations of a stream.------ @since 0.6.0-{-# DEPRECATED runN "Please use \"drainN\" instead" #-}-{-# INLINE runN #-}-runN :: Monad m => Int -> SerialT m a -> m ()-runN = drainN---- |--- > drainWhile p = drain . takeWhile p------ Run a stream as long as the predicate holds true.------ @since 0.7.0-{-# INLINE drainWhile #-}-drainWhile :: Monad m => (a -> Bool) -> SerialT m a -> m ()-drainWhile p = drain . takeWhile p---- |--- > runWhile p = runStream . takeWhile p------ Run a stream as long as the predicate holds true.------ @since 0.6.0-{-# DEPRECATED runWhile "Please use \"drainWhile\" instead" #-}-{-# INLINE runWhile #-}-runWhile :: Monad m => (a -> Bool) -> SerialT m a -> m ()-runWhile = drainWhile---- | Determine whether the stream is empty.------ @since 0.1.1-{-# INLINE null #-}-null :: Monad m => SerialT m a -> m Bool-null = S.null . toStreamS---- | Extract the first element of the stream, if any.------ > head = (!! 0)------ @since 0.1.0-{-# INLINE head #-}-head :: Monad m => SerialT m a -> m (Maybe a)-head = S.head . toStreamS---- |--- > tail = fmap (fmap snd) . uncons------ Extract all but the first element of the stream, if any.------ @since 0.1.1-{-# INLINE tail #-}-tail :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (t m a))-tail m = K.tail (K.adapt m)---- | Extract all but the last element of the stream, if any.------ @since 0.5.0-{-# INLINE init #-}-init :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (t m a))-init m = K.init (K.adapt m)---- | Extract the last element of the stream, if any.------ > last xs = xs !! (length xs - 1)------ @since 0.1.1-{-# INLINE last #-}-last :: Monad m => SerialT m a -> m (Maybe a)-last m = S.last $ toStreamS m---- | Determine whether an element is present in the stream.------ @since 0.1.0-{-# INLINE elem #-}-elem :: (Monad m, Eq a) => a -> SerialT m a -> m Bool-elem e m = S.elem e (toStreamS m)---- | Determine whether an element is not present in the stream.------ @since 0.1.0-{-# INLINE notElem #-}-notElem :: (Monad m, Eq a) => a -> SerialT m a -> m Bool-notElem e m = S.notElem e (toStreamS m)---- | Determine the length of the stream.------ @since 0.1.0-{-# INLINE length #-}-length :: Monad m => SerialT m a -> m Int-length = foldl' (\n _ -> n + 1) 0---- | Determine whether all elements of a stream satisfy a predicate.------ @since 0.1.0-{-# INLINE all #-}-all :: Monad m => (a -> Bool) -> SerialT m a -> m Bool-all p m = S.all p (toStreamS m)---- | Determine whether any of the elements of a stream satisfy a predicate.------ @since 0.1.0-{-# INLINE any #-}-any :: Monad m => (a -> Bool) -> SerialT m a -> m Bool-any p m = S.any p (toStreamS m)---- | Determines if all elements of a boolean stream are True.------ @since 0.5.0-{-# INLINE and #-}-and :: Monad m => SerialT m Bool -> m Bool-and = all (==True)---- | Determines whether at least one element of a boolean stream is True.------ @since 0.5.0-{-# INLINE or #-}-or :: Monad m => SerialT m Bool -> m Bool-or = any (==True)---- | Determine the sum of all elements of a stream of numbers. Returns @0@ when--- the stream is empty. Note that this is not numerically stable for floating--- point numbers.------ @since 0.1.0-{-# INLINE sum #-}-sum :: (Monad m, Num a) => SerialT m a -> m a-sum = foldl' (+) 0---- | Determine the product of all elements of a stream of numbers. Returns @1@--- when the stream is empty.------ @since 0.1.1-{-# INLINE product #-}-product :: (Monad m, Num a) => SerialT m a -> m a-product = foldl' (*) 1---- |--- @--- minimum = 'minimumBy' compare--- @------ Determine the minimum element in a stream.------ @since 0.1.0-{-# INLINE minimum #-}-minimum :: (Monad m, Ord a) => SerialT m a -> m (Maybe a)-minimum m = S.minimum (toStreamS m)---- | Determine the minimum element in a stream using the supplied comparison--- function.------ @since 0.6.0-{-# INLINE minimumBy #-}-minimumBy :: Monad m => (a -> a -> Ordering) -> SerialT m a -> m (Maybe a)-minimumBy cmp m = S.minimumBy cmp (toStreamS m)---- |--- @--- maximum = 'maximumBy' compare--- @------ Determine the maximum element in a stream.------ @since 0.1.0-{-# INLINE maximum #-}-maximum :: (Monad m, Ord a) => SerialT m a -> m (Maybe a)-maximum m = S.maximum (toStreamS m)---- | Determine the maximum element in a stream using the supplied comparison--- function.------ @since 0.6.0-{-# INLINE maximumBy #-}-maximumBy :: Monad m => (a -> a -> Ordering) -> SerialT m a -> m (Maybe a)-maximumBy cmp m = S.maximumBy cmp (toStreamS m)---- | Lookup the element at the given index.------ @since 0.6.0-{-# INLINE (!!) #-}-(!!) :: Monad m => SerialT m a -> Int -> m (Maybe a)-m !! i = toStreamS m S.!! i---- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the--- first pair where the key equals the given value @a@.------ > lookup = snd <$> find ((==) . fst)------ @since 0.5.0-{-# INLINE lookup #-}-lookup :: (Monad m, Eq a) => a -> SerialT m (a, b) -> m (Maybe b)-lookup a m = S.lookup a (toStreamS m)---- | Like 'findM' but with a non-monadic predicate.------ > find p = findM (return . p)------ @since 0.5.0-{-# INLINE find #-}-find :: Monad m => (a -> Bool) -> SerialT m a -> m (Maybe a)-find p m = S.find p (toStreamS m)---- | Returns the first element that satisfies the given predicate.------ @since 0.6.0-{-# INLINE findM #-}-findM :: Monad m => (a -> m Bool) -> SerialT m a -> m (Maybe a)-findM p m = S.findM p (toStreamS m)---- | Find all the indices where the element in the stream satisfies the given--- predicate.------ @since 0.5.0-{-# INLINE findIndices #-}-findIndices :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m Int-findIndices p m = fromStreamS $ S.findIndices p (toStreamS m)---- | Returns the first index that satisfies the given predicate.------ @since 0.5.0-{-# INLINE findIndex #-}-findIndex :: Monad m => (a -> Bool) -> SerialT m a -> m (Maybe Int)-findIndex p = head . findIndices p---- | Find all the indices where the value of the element in the stream is equal--- to the given value.------ @since 0.5.0-{-# INLINE elemIndices #-}-elemIndices :: (IsStream t, Eq a, Monad m) => a -> t m a -> t m Int-elemIndices a = findIndices (==a)---- | Returns the first index where a given value is found in the stream.------ > elemIndex a = findIndex (== a)------ @since 0.5.0-{-# INLINE elemIndex #-}-elemIndex :: (Monad m, Eq a) => a -> SerialT m a -> m (Maybe Int)-elemIndex a = findIndex (== a)----------------------------------------------------------------------------------- Substreams----------------------------------------------------------------------------------- | Returns 'True' if the first stream is the same as or a prefix of the--- second. A stream is a prefix of itself.------ @--- > S.isPrefixOf (S.fromList "hello") (S.fromList "hello" :: SerialT IO Char)--- True--- @------ @since 0.6.0-{-# INLINE isPrefixOf #-}-isPrefixOf :: (Eq a, IsStream t, Monad m) => t m a -> t m a -> m Bool-isPrefixOf m1 m2 = D.isPrefixOf (toStreamD m1) (toStreamD m2)---- | Returns 'True' if all the elements of the first stream occur, in order, in--- the second stream. The elements do not have to occur consecutively. A stream--- is a subsequence of itself.------ @--- > S.isSubsequenceOf (S.fromList "hlo") (S.fromList "hello" :: SerialT IO Char)--- True--- @------ @since 0.6.0-{-# INLINE isSubsequenceOf #-}-isSubsequenceOf :: (Eq a, IsStream t, Monad m) => t m a -> t m a -> m Bool-isSubsequenceOf m1 m2 = D.isSubsequenceOf (toStreamD m1) (toStreamD m2)---- | Drops the given prefix from a stream. Returns 'Nothing' if the stream does--- not start with the given prefix. Returns @Just nil@ when the prefix is the--- same as the stream.------ @since 0.6.0-{-# INLINE stripPrefix #-}-stripPrefix- :: (Eq a, IsStream t, Monad m)- => t m a -> t m a -> m (Maybe (t m a))-stripPrefix m1 m2 = fmap fromStreamD <$>- D.stripPrefix (toStreamD m1) (toStreamD m2)----------------------------------------------------------------------------------- Map and Fold----------------------------------------------------------------------------------- XXX this can utilize parallel mapping if we implement it as drain . mapM--- |--- > mapM_ = drain . mapM------ Apply a monadic action to each element of the stream and discard the output--- of the action. This is not really a pure transformation operation but a--- transformation followed by fold.------ @since 0.1.0-{-# INLINE mapM_ #-}-mapM_ :: Monad m => (a -> m b) -> SerialT m a -> m ()-mapM_ f m = S.mapM_ f $ toStreamS m----------------------------------------------------------------------------------- Conversions----------------------------------------------------------------------------------- |--- @--- toList = S.foldr (:) []--- @------ Convert a stream into a list in the underlying monad. The list can be--- consumed lazily in a lazy monad (e.g. 'Identity'). In a strict monad (e.g.--- IO) the whole list is generated and buffered before it can be consumed.------ /Warning!/ working on large lists accumulated as buffers in memory could be--- very inefficient, consider using "Streamly.Array" instead.------ @since 0.1.0-{-# INLINE toList #-}-toList :: Monad m => SerialT m a -> m [a]-toList = P.toList---- |--- @--- toListRev = S.foldl' (flip (:)) []--- @------ Convert a stream into a list in reverse order in the underlying monad.------ /Warning!/ working on large lists accumulated as buffers in memory could be--- very inefficient, consider using "Streamly.Array" instead.------ /Internal/-{-# INLINE toListRev #-}-toListRev :: Monad m => SerialT m a -> m [a]-toListRev = D.toListRev . toStreamD---- |--- @--- toHandle h = S.mapM_ $ hPutStrLn h--- @------ Write a stream of Strings to an IO Handle.------ @since 0.1.0-{-# DEPRECATED toHandle- "Please use Streamly.FileSystem.Handle module (see the changelog)" #-}-toHandle :: MonadIO m => IO.Handle -> SerialT m String -> m ()-toHandle h m = go m- where- go m1 =- let stop = return ()- single a = liftIO (IO.hPutStrLn h a)- yieldk a r = liftIO (IO.hPutStrLn h a) >> go r- in K.foldStream defState yieldk single stop m1---- XXX rename these to write/writeRev to make the naming consistent with folds--- in other modules.------ | A fold that buffers its input to a pure stream.------ /Warning!/ working on large streams accumulated as buffers in memory could--- be very inefficient, consider using "Streamly.Array" instead.------ /Internal/-{-# INLINE toStream #-}-toStream :: Monad m => Fold m a (SerialT Identity a)-toStream = Fold (\f x -> return $ f . (x `K.cons`))- (return id)- (return . ($ K.nil))---- This is more efficient than 'toStream'. toStream is exactly the same as--- reversing the stream after toStreamRev.------ | Buffers the input stream to a pure stream in the reverse order of the--- input.------ /Warning!/ working on large streams accumulated as buffers in memory could--- be very inefficient, consider using "Streamly.Array" instead.------ /Internal/---- xn : ... : x2 : x1 : []-{-# INLINABLE toStreamRev #-}-toStreamRev :: Monad m => Fold m a (SerialT Identity a)-toStreamRev = Fold (\xs x -> return $ x `K.cons` xs) (return K.nil) return---- | Convert a stream to a pure stream.------ @--- toPure = foldr cons nil--- @------ /Internal/----{-# INLINE toPure #-}-toPure :: Monad m => SerialT m a -> m (SerialT Identity a)-toPure = foldr K.cons K.nil---- | Convert a stream to a pure stream in reverse order.------ @--- toPureRev = foldl' (flip cons) nil--- @------ /Internal/----{-# INLINE toPureRev #-}-toPureRev :: Monad m => SerialT m a -> m (SerialT Identity a)-toPureRev = foldl' (flip K.cons) K.nil----------------------------------------------------------------------------------- General Transformation----------------------------------------------------------------------------------- | Use a 'Pipe' to transform a stream.-{-# INLINE transform #-}-transform :: (IsStream t, Monad m) => Pipe m a b -> t m a -> t m b-transform pipe xs = fromStreamD $ D.transform pipe (toStreamD xs)----------------------------------------------------------------------------------- Transformation by Folding (Scans)----------------------------------------------------------------------------------- XXX It may be useful to have a version of scan where we can keep the--- accumulator independent of the value emitted. So that we do not necessarily--- have to keep a value in the accumulator which we are not using. We can pass--- an extraction function that will take the accumulator and the current value--- of the element and emit the next value in the stream. That will also make it--- possible to modify the accumulator after using it. In fact, the step function--- can return new accumulator and the value to be emitted. The signature would--- be more like mapAccumL. Or we can change the signature of scanx to--- accommodate this.------ | Strict left scan with an extraction function. Like 'scanl'', but applies a--- user supplied extraction function (the third argument) at each step. This is--- designed to work with the @foldl@ library. The suffix @x@ is a mnemonic for--- extraction.------ /Since: 0.7.0 (Monad m constraint)/------ /Since 0.2.0/-{-# DEPRECATED scanx "Please use scanl followed by map instead." #-}-{-# INLINE scanx #-}-scanx :: (IsStream t, Monad m) => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b-scanx = P.scanlx'---- XXX this needs to be concurrent--- | Like 'scanl'' but with a monadic fold function.------ @since 0.4.0-{-# INLINE scanlM' #-}-scanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> t m b-scanlM' step begin m = fromStreamD $ D.scanlM' step begin $ toStreamD m---- | Strict left scan. Like 'map', 'scanl'' too is a one to one transformation,--- however it adds an extra element.------ @--- > S.toList $ S.scanl' (+) 0 $ fromList [1,2,3,4]--- [0,1,3,6,10]--- @------ @--- > S.toList $ S.scanl' (flip (:)) [] $ S.fromList [1,2,3,4]--- [[],[1],[2,1],[3,2,1],[4,3,2,1]]--- @------ The output of 'scanl'' is the initial value of the accumulator followed by--- all the intermediate steps and the final result of 'foldl''.------ By streaming the accumulated state after each fold step, we can share the--- state across multiple stages of stream composition. Each stage can modify or--- extend the state, do some processing with it and emit it for the next stage,--- thus modularizing the stream processing. This can be useful in--- stateful or event-driven programming.------ Consider the following monolithic example, computing the sum and the product--- of the elements in a stream in one go using a @foldl'@:------ @--- > S.foldl' (\\(s, p) x -> (s + x, p * x)) (0,1) $ S.fromList \[1,2,3,4]--- (10,24)--- @------ Using @scanl'@ we can make it modular by computing the sum in the first--- stage and passing it down to the next stage for computing the product:------ @--- > S.foldl' (\\(_, p) (s, x) -> (s, p * x)) (0,1)--- $ S.scanl' (\\(s, _) x -> (s + x, x)) (0,1)--- $ S.fromList \[1,2,3,4]--- (10,24)--- @------ IMPORTANT: 'scanl'' evaluates the accumulator to WHNF. To avoid building--- lazy expressions inside the accumulator, it is recommended that a strict--- data structure is used for accumulator.------ @since 0.2.0-{-# INLINE scanl' #-}-scanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b-scanl' step z m = fromStreamS $ S.scanl' step z $ toStreamS m---- | Like 'scanl'' but does not stream the initial value of the accumulator.------ > postscanl' f z xs = S.drop 1 $ S.scanl' f z xs------ @since 0.7.0-{-# INLINE postscanl' #-}-postscanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b-postscanl' step z m = fromStreamD $ D.postscanl' step z $ toStreamD m---- XXX this needs to be concurrent--- | Like 'postscanl'' but with a monadic step function.------ @since 0.7.0-{-# INLINE postscanlM' #-}-postscanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> t m b-postscanlM' step z m = fromStreamD $ D.postscanlM' step z $ toStreamD m---- XXX prescanl does not sound very useful, enable only if there is a--- compelling use case.------ | Like scanl' but does not stream the final value of the accumulator.------ @since 0.6.0-{-# INLINE prescanl' #-}-prescanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b-prescanl' step z m = fromStreamD $ D.prescanl' step z $ toStreamD m---- XXX this needs to be concurrent--- | Like postscanl' but with a monadic step function.------ @since 0.6.0-{-# INLINE prescanlM' #-}-prescanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> m b -> t m a -> t m b-prescanlM' step z m = fromStreamD $ D.prescanlM' step z $ toStreamD m---- XXX this needs to be concurrent--- | Like 'scanl1'' but with a monadic step function.------ @since 0.6.0-{-# INLINE scanl1M' #-}-scanl1M' :: (IsStream t, Monad m) => (a -> a -> m a) -> t m a -> t m a-scanl1M' step m = fromStreamD $ D.scanl1M' step $ toStreamD m---- | Like 'scanl'' but for a non-empty stream. The first element of the stream--- is used as the initial value of the accumulator. Does nothing if the stream--- is empty.------ @--- > S.toList $ S.scanl1 (+) $ fromList [1,2,3,4]--- [1,3,6,10]--- @------ @since 0.6.0-{-# INLINE scanl1' #-}-scanl1' :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> t m a-scanl1' step m = fromStreamD $ D.scanl1' step $ toStreamD m----------------------------------------------------------------------------------- Scanning with a Fold----------------------------------------------------------------------------------- | Scan a stream using the given monadic fold.------ @since 0.7.0-{-# INLINE scan #-}-scan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b-scan (Fold step begin done) = P.scanlMx' step begin done---- | Postscan a stream using the given monadic fold.------ @since 0.7.0-{-# INLINE postscan #-}-postscan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b-postscan (Fold step begin done) = P.postscanlMx' step begin done----------------------------------------------------------------------------------- Transformation by Filtering----------------------------------------------------------------------------------- | Include only those elements that pass a predicate.------ @since 0.1.0-{-# INLINE filter #-}-#if __GLASGOW_HASKELL__ != 802--- GHC 8.2.2 crashes with this code, when used with "stack"-filter :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a-filter p m = fromStreamS $ S.filter p $ toStreamS m-#else-filter :: IsStream t => (a -> Bool) -> t m a -> t m a-filter = K.filter-#endif---- | Same as 'filter' but with a monadic predicate.------ @since 0.4.0-{-# INLINE filterM #-}-filterM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a-filterM p m = fromStreamD $ D.filterM p $ toStreamD m---- | Drop repeated elements that are adjacent to each other.------ @since 0.6.0-{-# INLINE uniq #-}-uniq :: (Eq a, IsStream t, Monad m) => t m a -> t m a-uniq = fromStreamD . D.uniq . toStreamD---- | Ensures that all the elements of the stream are identical and then returns--- that unique element.------ @since 0.6.0-{-# INLINE the #-}-the :: (Eq a, Monad m) => SerialT m a -> m (Maybe a)-the m = S.the (toStreamS m)---- | Take first 'n' elements from the stream and discard the rest.------ @since 0.1.0-{-# INLINE take #-}-take :: (IsStream t, Monad m) => Int -> t m a -> t m a-take n m = fromStreamS $ S.take n $ toStreamS- (maxYields (Just (fromIntegral n)) m)---- | End the stream as soon as the predicate fails on an element.------ @since 0.1.0-{-# INLINE takeWhile #-}-takeWhile :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a-takeWhile p m = fromStreamS $ S.takeWhile p $ toStreamS m---- | Same as 'takeWhile' but with a monadic predicate.------ @since 0.4.0-{-# INLINE takeWhileM #-}-takeWhileM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a-takeWhileM p m = fromStreamD $ D.takeWhileM p $ toStreamD m---- | Discard first 'n' elements from the stream and take the rest.------ @since 0.1.0-{-# INLINE drop #-}-drop :: (IsStream t, Monad m) => Int -> t m a -> t m a-drop n m = fromStreamS $ S.drop n $ toStreamS m---- | Drop elements in the stream as long as the predicate succeeds and then--- take the rest of the stream.------ @since 0.1.0-{-# INLINE dropWhile #-}-dropWhile :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a-dropWhile p m = fromStreamS $ S.dropWhile p $ toStreamS m---- | Same as 'dropWhile' but with a monadic predicate.------ @since 0.4.0-{-# INLINE dropWhileM #-}-dropWhileM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a-dropWhileM p m = fromStreamD $ D.dropWhileM p $ toStreamD m----------------------------------------------------------------------------------- Transformation by Mapping----------------------------------------------------------------------------------- |--- @--- mapM f = sequence . map f--- @------ Apply a monadic function to each element of the stream and replace it with--- the output of the resulting action.------ @--- > drain $ S.mapM putStr $ S.fromList ["a", "b", "c"]--- abc------ drain $ S.replicateM 10 (return 1)--- & (serially . S.mapM (\\x -> threadDelay 1000000 >> print x))------ drain $ S.replicateM 10 (return 1)--- & (asyncly . S.mapM (\\x -> threadDelay 1000000 >> print x))--- @------ /Concurrent (do not use with 'parallely' on infinite streams)/------ @since 0.1.0-{-# INLINE_EARLY mapM #-}-mapM :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m b-mapM = K.mapM--{-# RULES "mapM serial" mapM = mapMSerial #-}-{-# INLINE mapMSerial #-}-mapMSerial :: Monad m => (a -> m b) -> SerialT m a -> SerialT m b-mapMSerial = Serial.mapM---- |--- @--- sequence = mapM id--- @------ Replace the elements of a stream of monadic actions with the outputs of--- those actions.------ @--- > drain $ S.sequence $ S.fromList [putStr "a", putStr "b", putStrLn "c"]--- abc------ drain $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)--- & (serially . S.sequence)------ drain $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)--- & (asyncly . S.sequence)--- @------ /Concurrent (do not use with 'parallely' on infinite streams)/------ @since 0.1.0-{-# INLINE sequence #-}-sequence :: (IsStream t, MonadAsync m) => t m (m a) -> t m a-sequence m = fromStreamS $ S.sequence (toStreamS m)----------------------------------------------------------------------------------- Transformation by Map and Filter----------------------------------------------------------------------------------- | Map a 'Maybe' returning function to a stream, filter out the 'Nothing'--- elements, and return a stream of values extracted from 'Just'.------ Equivalent to:------ @--- mapMaybe f = S.map 'fromJust' . S.filter 'isJust' . S.map f--- @------ @since 0.3.0-{-# INLINE mapMaybe #-}-mapMaybe :: (IsStream t, Monad m) => (a -> Maybe b) -> t m a -> t m b-mapMaybe f m = fromStreamS $ S.mapMaybe f $ toStreamS m---- | Like 'mapMaybe' but maps a monadic function.------ Equivalent to:------ @--- mapMaybeM f = S.map 'fromJust' . S.filter 'isJust' . S.mapM f--- @------ /Concurrent (do not use with 'parallely' on infinite streams)/------ @since 0.3.0-{-# INLINE_EARLY mapMaybeM #-}-mapMaybeM :: (IsStream t, MonadAsync m, Functor (t m))- => (a -> m (Maybe b)) -> t m a -> t m b-mapMaybeM f = fmap fromJust . filter isJust . K.mapM f--{-# RULES "mapMaybeM serial" mapMaybeM = mapMaybeMSerial #-}-{-# INLINE mapMaybeMSerial #-}-mapMaybeMSerial :: Monad m => (a -> m (Maybe b)) -> SerialT m a -> SerialT m b-mapMaybeMSerial f m = fromStreamD $ D.mapMaybeM f $ toStreamD m----------------------------------------------------------------------------------- Transformation by Reordering----------------------------------------------------------------------------------- XXX Use a compact region list to temporarily store the list, in both reverse--- as well as in reverse'.------ /Note:/ 'reverse'' is much faster than this, use that when performance--- matters.------ > reverse = S.foldlT (flip S.cons) S.nil------ | Returns the elements of the stream in reverse order. The stream must be--- finite. Note that this necessarily buffers the entire stream in memory.------ /Since 0.7.0 (Monad m constraint)/------ /Since: 0.1.1/-{-# INLINE reverse #-}-reverse :: (IsStream t, Monad m) => t m a -> t m a-reverse s = fromStreamS $ S.reverse $ toStreamS s---- | Like 'reverse' but several times faster, requires a 'Storable' instance.------ @since 0.7.0-{-# INLINE reverse' #-}-reverse' :: (IsStream t, MonadIO m, Storable a) => t m a -> t m a-reverse' s = fromStreamD $ D.reverse' $ toStreamD s----------------------------------------------------------------------------------- Transformation by Inserting----------------------------------------------------------------------------------- intersperseM = intersperseBySpan 1---- | Generate a stream by performing a monadic action between consecutive--- elements of the given stream.------ /Concurrent (do not use with 'parallely' on infinite streams)/------ @--- > S.toList $ S.intersperseM (return ',') $ S.fromList "hello"--- "h,e,l,l,o"--- @------ @since 0.5.0-{-# INLINE intersperseM #-}-intersperseM :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a-intersperseM m = fromStreamS . S.intersperseM m . toStreamS---- | Generate a stream by inserting a given element between consecutive--- elements of the given stream.------ @--- > S.toList $ S.intersperse ',' $ S.fromList "hello"--- "h,e,l,l,o"--- @------ @since 0.7.0-{-# INLINE intersperse #-}-intersperse :: (IsStream t, MonadAsync m) => a -> t m a -> t m a-intersperse a = fromStreamS . S.intersperse a . toStreamS---- | Insert a monadic action after each element in the stream.------ @since 0.7.0-{-# INLINE intersperseSuffix #-}-intersperseSuffix :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a-intersperseSuffix m = fromStreamD . D.intersperseSuffix m . toStreamD--{---- | Intersperse a monadic action into the input stream after every @n@--- elements.------ @--- > S.toList $ S.intersperseBySpan 2 (return ',') $ S.fromList "hello"--- "he,ll,o"--- @------ @since 0.7.0-{-# INLINE intersperseBySpan #-}-intersperseBySpan :: IsStream t => Int -> m a -> t m a -> t m a-intersperseBySpan _n _f _xs = undefined--}---- | Intersperse a monadic action into the input stream after every @n@--- seconds.------ @--- > S.drain $ S.interjectSuffix 1 (putChar ',') $ S.mapM (\\x -> threadDelay 1000000 >> putChar x) $ S.fromList "hello"--- "h,e,l,l,o"--- @------ @since 0.7.0-{-# INLINE interjectSuffix #-}-interjectSuffix- :: (IsStream t, MonadAsync m)- => Double -> m a -> t m a -> t m a-interjectSuffix n f xs = xs `Par.parallelFst` repeatM timed- where timed = liftIO (threadDelay (round $ n * 1000000)) >> f---- | @insertBy cmp elem stream@ inserts @elem@ before the first element in--- @stream@ that is less than @elem@ when compared using @cmp@.------ @--- insertBy cmp x = 'mergeBy' cmp ('yield' x)--- @------ @--- > S.toList $ S.insertBy compare 2 $ S.fromList [1,3,5]--- [1,2,3,5]--- @------ @since 0.6.0-{-# INLINE insertBy #-}-insertBy ::- (IsStream t, Monad m) => (a -> a -> Ordering) -> a -> t m a -> t m a-insertBy cmp x m = fromStreamS $ S.insertBy cmp x (toStreamS m)----------------------------------------------------------------------------------- Deleting----------------------------------------------------------------------------------- | Deletes the first occurence of the element in the stream that satisfies--- the given equality predicate.------ @--- > S.toList $ S.deleteBy (==) 3 $ S.fromList [1,3,3,5]--- [1,3,5]--- @------ @since 0.6.0-{-# INLINE deleteBy #-}-deleteBy :: (IsStream t, Monad m) => (a -> a -> Bool) -> a -> t m a -> t m a-deleteBy cmp x m = fromStreamS $ S.deleteBy cmp x (toStreamS m)----------------------------------------------------------------------------------- Zipping----------------------------------------------------------------------------------- |--- > indexed = S.postscanl' (\(i, _) x -> (i + 1, x)) (-1,undefined)--- > indexed = S.zipWith (,) (S.enumerateFrom 0)------ Pair each element in a stream with its index, starting from index 0.------ @--- > S.toList $ S.indexed $ S.fromList "hello"--- [(0,'h'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]--- @------ @since 0.6.0-{-# INLINE indexed #-}-indexed :: (IsStream t, Monad m) => t m a -> t m (Int, a)-indexed = fromStreamD . D.indexed . toStreamD---- |--- > indexedR n = S.postscanl' (\(i, _) x -> (i - 1, x)) (n + 1,undefined)--- > indexedR n = S.zipWith (,) (S.enumerateFromThen n (n - 1))------ Pair each element in a stream with its index, starting from the--- given index @n@ and counting down.------ @--- > S.toList $ S.indexedR 10 $ S.fromList "hello"--- [(10,'h'),(9,'e'),(8,'l'),(7,'l'),(6,'o')]--- @------ @since 0.6.0-{-# INLINE indexedR #-}-indexedR :: (IsStream t, Monad m) => Int -> t m a -> t m (Int, a)-indexedR n = fromStreamD . D.indexedR n . toStreamD---- | Like 'zipWith' but using a monadic zipping function.------ @since 0.4.0-{-# INLINABLE zipWithM #-}-zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c-zipWithM f m1 m2 = fromStreamS $ S.zipWithM f (toStreamS m1) (toStreamS m2)---- | Zip two streams serially using a pure zipping function.------ @--- > S.toList $ S.zipWith (+) (S.fromList [1,2,3]) (S.fromList [4,5,6])--- [5,7,9]--- @------ @since 0.1.0-{-# INLINABLE zipWith #-}-zipWith :: (IsStream t, Monad m) => (a -> b -> c) -> t m a -> t m b -> t m c-zipWith f m1 m2 = fromStreamS $ S.zipWith f (toStreamS m1) (toStreamS m2)----------------------------------------------------------------------------------- Comparison----------------------------------------------------------------------------------- | Compare two streams for equality using an equality function.------ @since 0.6.0-{-# INLINABLE eqBy #-}-eqBy :: (IsStream t, Monad m) => (a -> b -> Bool) -> t m a -> t m b -> m Bool-eqBy = P.eqBy---- | Compare two streams lexicographically using a comparison function.------ @since 0.6.0-{-# INLINABLE cmpBy #-}-cmpBy- :: (IsStream t, Monad m)- => (a -> b -> Ordering) -> t m a -> t m b -> m Ordering-cmpBy = P.cmpBy----------------------------------------------------------------------------------- Merge----------------------------------------------------------------------------------- | Merge two streams using a comparison function. The head elements of both--- the streams are compared and the smaller of the two elements is emitted, if--- both elements are equal then the element from the first stream is used--- first.------ If the streams are sorted in ascending order, the resulting stream would--- also remain sorted in ascending order.------ @--- > S.toList $ S.mergeBy compare (S.fromList [1,3,5]) (S.fromList [2,4,6,8])--- [1,2,3,4,5,6,8]--- @------ @since 0.6.0-{-# INLINABLE mergeBy #-}-mergeBy ::- (IsStream t, Monad m) => (a -> a -> Ordering) -> t m a -> t m a -> t m a-mergeBy f m1 m2 = fromStreamS $ S.mergeBy f (toStreamS m1) (toStreamS m2)---- | Like 'mergeBy' but with a monadic comparison function.------ Merge two streams randomly:------ @--- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT--- > S.toList $ S.mergeByM randomly (S.fromList [1,1,1,1]) (S.fromList [2,2,2,2])--- [2,1,2,2,2,1,1,1]--- @------ Merge two streams in a proportion of 2:1:------ @--- proportionately m n = do--- ref <- newIORef $ cycle $ concat [replicate m LT, replicate n GT]--- return $ \\_ _ -> do--- r <- readIORef ref--- writeIORef ref $ tail r--- return $ head r------ main = do--- f <- proportionately 2 1--- xs <- S.toList $ S.mergeByM f (S.fromList [1,1,1,1,1,1]) (S.fromList [2,2,2])--- print xs--- @--- @--- [1,1,2,1,1,2,1,1,2]--- @------ @since 0.6.0-{-# INLINABLE mergeByM #-}-mergeByM- :: (IsStream t, Monad m)- => (a -> a -> m Ordering) -> t m a -> t m a -> t m a-mergeByM f m1 m2 = fromStreamS $ S.mergeByM f (toStreamS m1) (toStreamS m2)--{---- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.-{-# INLINABLE mergeEndByAny #-}-mergeEndByAny- :: (IsStream t, Monad m)- => (a -> a -> m Ordering) -> t m a -> t m a -> t m a-mergeEndByAny f m1 m2 = fromStreamD $- D.mergeEndByAny f (toStreamD m1) (toStreamD m2)---- Like 'mergeByM' but stops merging as soon as the first stream stops.-{-# INLINABLE mergeEndByFirst #-}-mergeEndByFirst- :: (IsStream t, Monad m)- => (a -> a -> m Ordering) -> t m a -> t m a -> t m a-mergeEndByFirst f m1 m2 = fromStreamS $- D.mergeEndByFirst f (toStreamD m1) (toStreamD m2)--}---- Holding this back for now, we may want to use the name "merge" differently-{---- | Same as @'mergeBy' 'compare'@.------ @--- > S.toList $ S.merge (S.fromList [1,3,5]) (S.fromList [2,4,6,8])--- [1,2,3,4,5,6,8]--- @------ @since 0.6.0-{-# INLINABLE merge #-}-merge ::- (IsStream t, Monad m, Ord a) => t m a -> t m a -> t m a-merge = mergeBy compare--}---- | Like 'mergeBy' but merges concurrently (i.e. both the elements being--- merged are generated concurrently).------ @since 0.6.0-mergeAsyncBy :: (IsStream t, MonadAsync m)- => (a -> a -> Ordering) -> t m a -> t m a -> t m a-mergeAsyncBy f m1 m2 = K.mkStream $ \st stp sng yld -> do- ma <- mkAsync' st m1- mb <- mkAsync' st m2- K.foldStream st stp sng yld (K.mergeBy f ma mb)---- | Like 'mergeByM' but merges concurrently (i.e. both the elements being--- merged are generated concurrently).------ @since 0.6.0-mergeAsyncByM :: (IsStream t, MonadAsync m)- => (a -> a -> m Ordering) -> t m a -> t m a -> t m a-mergeAsyncByM f m1 m2 = K.mkStream $ \st stp sng yld -> do- ma <- mkAsync' st m1- mb <- mkAsync' st m2- K.foldStream st stp sng yld (K.mergeByM f ma mb)----------------------------------------------------------------------------------- Nesting----------------------------------------------------------------------------------- | @concatMapWith merge map stream@ is a two dimensional looping combinator.--- The first argument specifies a merge or concat function that is used to--- merge the streams generated by applying the second argument i.e. the @map@--- function to each element of the input stream. The concat function could be--- 'serial', 'parallel', 'async', 'ahead' or any other zip or merge function--- and the second argument could be any stream generation function using a--- seed.------ /Compare 'foldMapWith'/------ @since 0.7.0-{-# INLINE concatMapWith #-}-concatMapWith- :: IsStream t- => (forall c. t m c -> t m c -> t m c)- -> (a -> t m b)- -> t m a- -> t m b-concatMapWith = K.concatMapBy---- | Map a stream producing function on each element of the stream and then--- flatten the results into a single stream.------ @--- concatMap = 'concatMapWith' 'Serial.serial'--- concatMap f = 'concatMapM' (return . f)--- @------ @since 0.6.0-{-# INLINE concatMap #-}-concatMap ::(IsStream t, Monad m) => (a -> t m b) -> t m a -> t m b-concatMap f m = fromStreamD $ D.concatMap (toStreamD . f) (toStreamD m)---- | Append the outputs of two streams, yielding all the elements from the--- first stream and then yielding all the elements from the second stream.------ IMPORTANT NOTE: This could be 100x faster than @serial/<>@ for appending a--- few (say 100) streams because it can fuse via stream fusion. However, it--- does not scale for a large number of streams (say 1000s) and becomes--- qudartically slow. Therefore use this for custom appending of a few streams--- but use 'concatMap' or 'concatMapWith serial' for appending @n@ streams or--- infinite containers of streams.------ @since 0.7.0-{-# INLINE append #-}-append ::(IsStream t, Monad m) => t m b -> t m b -> t m b-append m1 m2 = fromStreamD $ D.append (toStreamD m1) (toStreamD m2)---- XXX Same as 'wSerial'. We should perhaps rename wSerial to interleave.--- XXX Document the interleaving behavior of side effects in all the--- interleaving combinators.--- XXX Write time-domain equivalents of these. In the time domain we can--- interleave two streams such that the value of second stream is always taken--- from its last value even if no new value is being yielded, like--- zipWithLatest. It would be something like interleaveWithLatest.------ | Interleaves the outputs of two streams, yielding elements from each stream--- alternately, starting from the first stream. If any of the streams finishes--- early the other stream continues alone until it too finishes.------ >>> :set -XOverloadedStrings--- >>> interleave "ab" ",,,," :: SerialT Identity Char--- fromList "a,b,,,"--- >>> interleave "abcd" ",," :: SerialT Identity Char--- fromList "a,b,cd"------ 'interleave' is dual to 'interleaveMin', it can be called @interleaveMax@.------ Do not use at scale in concatMapWith.------ @since 0.7.0-{-# INLINE interleave #-}-interleave ::(IsStream t, Monad m) => t m b -> t m b -> t m b-interleave m1 m2 = fromStreamD $ D.interleave (toStreamD m1) (toStreamD m2)---- | Interleaves the outputs of two streams, yielding elements from each stream--- alternately, starting from the first stream. As soon as the first stream--- finishes, the output stops, discarding the remaining part of the second--- stream. In this case, the last element in the resulting stream would be from--- the second stream. If the second stream finishes early then the first stream--- still continues to yield elements until it finishes.------ >>> :set -XOverloadedStrings--- >>> interleaveSuffix "abc" ",,,," :: SerialT Identity Char--- fromList "a,b,c,"--- >>> interleaveSuffix "abc" "," :: SerialT Identity Char--- fromList "a,bc"------ 'interleaveSuffix' is a dual of 'interleaveInfix'.------ Do not use at scale in concatMapWith.------ @since 0.7.0-{-# INLINE interleaveSuffix #-}-interleaveSuffix ::(IsStream t, Monad m) => t m b -> t m b -> t m b-interleaveSuffix m1 m2 =- fromStreamD $ D.interleaveSuffix (toStreamD m1) (toStreamD m2)---- | Interleaves the outputs of two streams, yielding elements from each stream--- alternately, starting from the first stream and ending at the first stream.--- If the second stream is longer than the first, elements from the second--- stream are infixed with elements from the first stream. If the first stream--- is longer then it continues yielding elements even after the second stream--- has finished.------ >>> :set -XOverloadedStrings--- >>> interleaveInfix "abc" ",,,," :: SerialT Identity Char--- fromList "a,b,c"--- >>> interleaveInfix "abc" "," :: SerialT Identity Char--- fromList "a,bc"------ 'interleaveInfix' is a dual of 'interleaveSuffix'.------ Do not use at scale in concatMapWith.------ @since 0.7.0-{-# INLINE interleaveInfix #-}-interleaveInfix ::(IsStream t, Monad m) => t m b -> t m b -> t m b-interleaveInfix m1 m2 =- fromStreamD $ D.interleaveInfix (toStreamD m1) (toStreamD m2)---- | Interleaves the outputs of two streams, yielding elements from each stream--- alternately, starting from the first stream. The output stops as soon as any--- of the two streams finishes, discarding the remaining part of the other--- stream. The last element of the resulting stream would be from the longer--- stream.------ >>> :set -XOverloadedStrings--- >>> interleaveMin "ab" ",,,," :: SerialT Identity Char--- fromList "a,b,"--- >>> interleaveMin "abcd" ",," :: SerialT Identity Char--- fromList "a,b,c"------ 'interleaveMin' is dual to 'interleave'.------ Do not use at scale in concatMapWith.------ @since 0.7.0-{-# INLINE interleaveMin #-}-interleaveMin ::(IsStream t, Monad m) => t m b -> t m b -> t m b-interleaveMin m1 m2 = fromStreamD $ D.interleaveMin (toStreamD m1) (toStreamD m2)---- | Schedule the execution of two streams in a fair round-robin manner,--- executing each stream once, alternately. Execution of a stream may not--- necessarily result in an output, a stream may chose to @Skip@ producing an--- element until later giving the other stream a chance to run. Therefore, this--- combinator fairly interleaves the execution of two streams rather than--- fairly interleaving the output of the two streams. This can be useful in--- co-operative multitasking without using explicit threads. This can be used--- as an alternative to `async`.------ Do not use at scale in concatMapWith.------ @since 0.7.0-{-# INLINE roundrobin #-}-roundrobin ::(IsStream t, Monad m) => t m b -> t m b -> t m b-roundrobin m1 m2 = fromStreamD $ D.roundRobin (toStreamD m1) (toStreamD m2)---- | Map a stream producing monadic function on each element of the stream--- and then flatten the results into a single stream. Since the stream--- generation function is monadic, unlike 'concatMap', it can produce an--- effect at the beginning of each iteration of the inner loop.------ @since 0.6.0-{-# INLINE concatMapM #-}-concatMapM :: (IsStream t, Monad m) => (a -> m (t m b)) -> t m a -> t m b-concatMapM f m = fromStreamD $ D.concatMapM (fmap toStreamD . f) (toStreamD m)---- | Like 'concatMap' but uses an 'Unfold' for stream generation. Unlike--- 'concatMap' this can fuse the 'Unfold' code with the inner loop and--- therefore provide many times better performance.------ @since 0.7.0-{-# INLINE concatUnfold #-}-concatUnfold ::(IsStream t, Monad m) => Unfold m a b -> t m a -> t m b-concatUnfold u m = fromStreamD $ D.concatMapU u (toStreamD m)---- | Like 'concatUnfold' but interleaves the streams in the same way as--- 'interleave' behaves instead of appending them.------ @since 0.7.0-{-# INLINE concatUnfoldInterleave #-}-concatUnfoldInterleave ::(IsStream t, Monad m)- => Unfold m a b -> t m a -> t m b-concatUnfoldInterleave u m =- fromStreamD $ D.concatUnfoldInterleave u (toStreamD m)---- | Like 'concatUnfold' but executes the streams in the same way as--- 'roundrobin'.------ @since 0.7.0-{-# INLINE concatUnfoldRoundrobin #-}-concatUnfoldRoundrobin ::(IsStream t, Monad m)- => Unfold m a b -> t m a -> t m b-concatUnfoldRoundrobin u m =- fromStreamD $ D.concatUnfoldRoundrobin u (toStreamD m)---- XXX we can swap the order of arguments to gintercalate so that the--- definition of concatUnfold becomes simpler? The first stream should be--- infixed inside the second one. However, if we change the order in--- "interleave" as well similarly, then that will make it a bit unintuitive.------ > concatUnfold unf str =--- > gintercalate unf str (UF.nilM (\_ -> return ())) (repeat ())------ | 'interleaveInfix' followed by unfold and concat.------ /Internal/-{-# INLINE gintercalate #-}-gintercalate- :: (IsStream t, Monad m)- => Unfold m a c -> t m a -> Unfold m b c -> t m b -> t m c-gintercalate unf1 str1 unf2 str2 =- D.fromStreamD $ D.gintercalate- unf1 (D.toStreamD str1)- unf2 (D.toStreamD str2)---- XXX The order of arguments in "intercalate" is consistent with the list--- intercalate but inconsistent with gintercalate and other stream interleaving--- combinators. We can change the order of the arguments in other combinators--- but then 'interleave' combinator may become a bit unintuitive because we--- will be starting with the second stream.---- > intercalate seed unf str = gintercalate unf str unf (repeatM seed)--- > intercalate a unf str = concatUnfold unf $ intersperse a str------ | 'intersperse' followed by unfold and concat.------ > unwords = intercalate " " UF.fromList------ >>> intercalate " " UF.fromList ["abc", "def", "ghi"]--- > "abc def ghi"----{-# INLINE intercalate #-}-intercalate :: (IsStream t, Monad m)- => b -> Unfold m b c -> t m b -> t m c-intercalate seed unf str = D.fromStreamD $- D.concatMapU unf $ D.intersperse seed (toStreamD str)---- > interpose x unf str = gintercalate unf str UF.identity (repeat x)------ | Unfold the elements of a stream, intersperse the given element between the--- unfolded streams and then concat them into a single stream.------ > unwords = S.interpose ' '------ /Internal/-{-# INLINE interpose #-}-interpose :: (IsStream t, Monad m)- => c -> Unfold m b c -> t m b -> t m c-interpose x unf str =- D.fromStreamD $ D.interpose (return x) unf (D.toStreamD str)---- | 'interleaveSuffix' followed by unfold and concat.------ /Internal/-{-# INLINE gintercalateSuffix #-}-gintercalateSuffix- :: (IsStream t, Monad m)- => Unfold m a c -> t m a -> Unfold m b c -> t m b -> t m c-gintercalateSuffix unf1 str1 unf2 str2 =- D.fromStreamD $ D.gintercalateSuffix- unf1 (D.toStreamD str1)- unf2 (D.toStreamD str2)---- > intercalateSuffix seed unf str = gintercalateSuffix unf str unf (repeatM seed)--- > intercalateSuffix a unf str = concatUnfold unf $ intersperseSuffix a str------ | 'intersperseSuffix' followed by unfold and concat.------ > unlines = intercalateSuffix "\n" UF.fromList------ >>> intercalate "\n" UF.fromList ["abc", "def", "ghi"]--- > "abc\ndef\nghi\n"----{-# INLINE intercalateSuffix #-}-intercalateSuffix :: (IsStream t, Monad m)- => b -> Unfold m b c -> t m b -> t m c-intercalateSuffix seed unf str = fromStreamD $ D.concatMapU unf- $ D.intersperseSuffix (return seed) (D.toStreamD str)---- interposeSuffix x unf str = gintercalateSuffix unf str UF.identity (repeat x)------ | Unfold the elements of a stream, append the given element after each--- unfolded stream and then concat them into a single stream.------ > unlines = S.interposeSuffix '\n'------ /Internal/-{-# INLINE interposeSuffix #-}-interposeSuffix :: (IsStream t, Monad m)- => c -> Unfold m b c -> t m b -> t m c-interposeSuffix x unf str =- D.fromStreamD $ D.interposeSuffix (return x) unf (D.toStreamD str)----------------------------------------------------------------------------------- Grouping/Splitting------------------------------------------------------------------------------------------------------------------------------------------------------------------ Grouping without looking at elements-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Binary APIs-------------------------------------------------------------------------------------- | @splitAt n f1 f2@ composes folds @f1@ and @f2@ such that first @n@--- elements of its input are consumed by fold @f1@ and the rest of the stream--- is consumed by fold @f2@.------ > let splitAt_ n xs = S.fold (FL.splitAt n FL.toList FL.toList) $ S.fromList xs------ >>> splitAt_ 6 "Hello World!"--- > ("Hello ","World!")------ >>> splitAt_ (-1) [1,2,3]--- > ([],[1,2,3])------ >>> splitAt_ 0 [1,2,3]--- > ([],[1,2,3])------ >>> splitAt_ 1 [1,2,3]--- > ([1],[2,3])------ >>> splitAt_ 3 [1,2,3]--- > ([1,2,3],[])------ >>> splitAt_ 4 [1,2,3]--- > ([1,2,3],[])------ @since 0.7.0---- This can be considered as a two-fold version of 'ltake' where we take both--- the segments instead of discarding the leftover.----{-# INLINE splitAt #-}-splitAt- :: Monad m- => Int- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-splitAt n (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract- where- initial = Tuple3' <$> return n <*> initialL <*> initialR-- step (Tuple3' i xL xR) input =- if i > 0- then stepL xL input >>= (\a -> return (Tuple3' (i - 1) a xR))- else stepR xR input >>= (\b -> return (Tuple3' i xL b))-- extract (Tuple3' _ a b) = (,) <$> extractL a <*> extractR b----------------------------------------------------------------------------------- N-ary APIs------------------------------------------------------------------------------------------------------------------------------------------------------------------ Generalized grouping----------------------------------------------------------------------------------- This combinator is the most general grouping combinator and can be used to--- implement all other grouping combinators.------ XXX check if this can implement the splitOn combinator i.e. we can slide in--- new elements, slide out old elements and incrementally compute the hash.--- Also, can we implement the windowed classification combinators using this?------ In fact this is a parse. Instead of using a special return value in the fold--- we are using a mapping function.------ Note that 'scanl'' (usually followed by a map to extract the desired value--- from the accumulator) can be used to realize many implementations e.g. a--- sliding window implementation. A scan followed by a mapMaybe is also a good--- pattern to express many problems where we want to emit a filtered output and--- not emit an output on every input.------ Passing on of the initial accumulator value to the next fold is equivalent--- to returning the leftover concept.--{---- | @groupScan splitter fold stream@ folds the input stream using @fold@.--- @splitter@ is applied on the accumulator of the fold every time an item is--- consumed by the fold. The fold continues until @splitter@ returns a 'Just'--- value. A 'Just' result from the @splitter@ specifies a result to be emitted--- in the output stream and the initial value of the accumulator for the next--- group's fold. This allows us to control whether to start fresh for the next--- fold or to continue from the previous fold's output.----{-# INLINE groupScan #-}-groupScan- :: (IsStream t, Monad m)- => (x -> m (Maybe (b, x))) -> Fold m a x -> t m a -> t m b-groupScan split fold m = undefined--}---- | Group the input stream into groups of @n@ elements each and then fold each--- group using the provided fold function.------ >> S.toList $ S.chunksOf 2 FL.sum (S.enumerateFromTo 1 10)--- > [3,7,11,15,19]------ This can be considered as an n-fold version of 'ltake' where we apply--- 'ltake' repeatedly on the leftover stream until the stream exhausts.------ @since 0.7.0-{-# INLINE chunksOf #-}-chunksOf- :: (IsStream t, Monad m)- => Int -> Fold m a b -> t m a -> t m b-chunksOf n f m = D.fromStreamD $ D.groupsOf n f (D.toStreamD m)--{-# INLINE chunksOf2 #-}-chunksOf2- :: (IsStream t, Monad m)- => Int -> m c -> Fold2 m c a b -> t m a -> t m b-chunksOf2 n action f m = D.fromStreamD $ D.groupsOf2 n action f (D.toStreamD m)---- | @arraysOf n stream@ groups the elements in the input stream into arrays of--- @n@ elements each.------ Same as the following but may be more efficient:------ > arraysOf n = S.chunksOf n (A.writeN n)------ @since 0.7.0-{-# INLINE arraysOf #-}-arraysOf :: (IsStream t, MonadIO m, Storable a)- => Int -> t m a -> t m (Array a)-arraysOf n = chunksOf n (writeNUnsafe n)---- XXX we can implement this by repeatedly applying the 'lrunFor' fold.--- XXX add this example after fixing the serial stream rate control--- >>> S.toList $ S.take 5 $ intervalsOf 1 FL.sum $ constRate 2 $ S.enumerateFrom 1--- > [3,7,11,15,19]------ | Group the input stream into windows of @n@ second each and then fold each--- group using the provided fold function.------ @since 0.7.0-{-# INLINE intervalsOf #-}-intervalsOf- :: (IsStream t, MonadAsync m)- => Double -> Fold m a b -> t m a -> t m b-intervalsOf n f xs =- splitWithSuffix isNothing (FL.lcatMaybes f)- (interjectSuffix n (return Nothing) (Serial.map Just xs))----------------------------------------------------------------------------------- Element Aware APIs-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Binary APIs----------------------------------------------------------------------------------- | Break the input stream into two groups, the first group takes the input as--- long as the predicate applied to the first element of the stream and next--- input element holds 'True', the second group takes the rest of the input.----spanBy- :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-spanBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract-- where- initial = Tuple3' <$> initialL <*> initialR <*> return (Tuple' Nothing True)-- step (Tuple3' a b (Tuple' (Just frst) isFirstG)) input =- if cmp frst input && isFirstG- then stepL a input- >>= (\a' -> return (Tuple3' a' b (Tuple' (Just frst) isFirstG)))- else stepR b input- >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))-- step (Tuple3' a b (Tuple' Nothing isFirstG)) input =- if isFirstG- then stepL a input- >>= (\a' -> return (Tuple3' a' b (Tuple' (Just input) isFirstG)))- else stepR b input- >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))-- extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b---- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the--- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the--- input.------ > let span_ p xs = S.fold (S.span p FL.toList FL.toList) $ S.fromList xs------ >>> span_ (< 1) [1,2,3]--- > ([],[1,2,3])------ >>> span_ (< 2) [1,2,3]--- > ([1],[2,3])------ >>> span_ (< 4) [1,2,3]--- > ([1,2,3],[])------ @since 0.7.0---- This can be considered as a two-fold version of 'ltakeWhile' where we take--- both the segments instead of discarding the leftover.-{-# INLINE span #-}-span- :: Monad m- => (a -> Bool)- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-span p (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract-- where-- initial = Tuple3' <$> initialL <*> initialR <*> return True-- step (Tuple3' a b isFirstG) input =- if isFirstG && p input- then stepL a input >>= (\a' -> return (Tuple3' a' b True))- else stepR b input >>= (\a' -> return (Tuple3' a a' False))-- extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b---- |--- > break p = span (not . p)------ Break as soon as the predicate becomes 'True'. @break p f1 f2@ composes--- folds @f1@ and @f2@ such that @f1@ stops consuming input as soon as the--- predicate @p@ becomes 'True'. The rest of the input is consumed @f2@.------ This is the binary version of 'splitBy'.------ > let break_ p xs = S.fold (S.break p FL.toList FL.toList) $ S.fromList xs------ >>> break_ (< 1) [3,2,1]--- > ([3,2,1],[])------ >>> break_ (< 2) [3,2,1]--- > ([3,2],[1])------ >>> break_ (< 4) [3,2,1]--- > ([],[3,2,1])------ @since 0.7.0-{-# INLINE break #-}-break- :: Monad m- => (a -> Bool)- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-break p = span (not . p)---- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.--- predicate is applied to the previous and the next input elements.-{-# INLINE spanByRolling #-}-spanByRolling- :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-spanByRolling cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract-- where- initial = Tuple3' <$> initialL <*> initialR <*> return Nothing-- step (Tuple3' a b (Just frst)) input =- if cmp input frst- then stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))- else stepR b input >>= (\b' -> return (Tuple3' a b' (Just input)))-- step (Tuple3' a b Nothing) input =- stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))-- extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b----------------------------------------------------------------------------------- N-ary APIs------------------------------------------------------------------------------------- | @groupsBy cmp f $ S.fromList [a,b,c,...]@ assigns the element @a@ to the--- first group, if @a \`cmp` b@ is 'True' then @b@ is also assigned to the same--- group. If @a \`cmp` c@ is 'True' then @c@ is also assigned to the same--- group and so on. When the comparison fails a new group is started. Each--- group is folded using the fold @f@ and the result of the fold is emitted in--- the output stream.------ >>> S.toList $ S.groupsBy (>) FL.toList $ S.fromList [1,3,7,0,2,5]--- > [[1,3,7],[0,2,5]]------ @since 0.7.0-{-# INLINE groupsBy #-}-groupsBy- :: (IsStream t, Monad m)- => (a -> a -> Bool)- -> Fold m a b- -> t m a- -> t m b-groupsBy cmp f m = D.fromStreamD $ D.groupsBy cmp f (D.toStreamD m)---- | Unlike @groupsBy@ this function performs a rolling comparison of two--- successive elements in the input stream. @groupsByRolling cmp f $ S.fromList--- [a,b,c,...]@ assigns the element @a@ to the first group, if @a \`cmp` b@ is--- 'True' then @b@ is also assigned to the same group. If @b \`cmp` c@ is--- 'True' then @c@ is also assigned to the same group and so on. When the--- comparison fails a new group is started. Each group is folded using the fold--- @f@.------ >>> S.toList $ S.groupsByRolling (\a b -> a + 1 == b) FL.toList $ S.fromList [1,2,3,7,8,9]--- > [[1,2,3],[7,8,9]]------ @since 0.7.0-{-# INLINE groupsByRolling #-}-groupsByRolling- :: (IsStream t, Monad m)- => (a -> a -> Bool)- -> Fold m a b- -> t m a- -> t m b-groupsByRolling cmp f m = D.fromStreamD $ D.groupsRollingBy cmp f (D.toStreamD m)---- |--- > groups = groupsBy (==)--- > groups = groupsByRolling (==)------ Groups contiguous spans of equal elements together in individual groups.------ >>> S.toList $ S.groups FL.toList $ S.fromList [1,1,2,2]--- > [[1,1],[2,2]]------ @since 0.7.0-groups :: (IsStream t, Monad m, Eq a) => Fold m a b -> t m a -> t m b-groups = groupsBy (==)----------------------------------------------------------------------------------- Binary splitting on a separator---------------------------------------------------------------------------------{---- | Find the first occurrence of the specified sequence in the input stream--- and break the input stream into two parts, the first part consisting of the--- stream before the sequence and the second part consisting of the sequence--- and the rest of the stream.------ > let breakOn_ pat xs = S.fold (S.breakOn pat FL.toList FL.toList) $ S.fromList xs------ >>> breakOn_ "dear" "Hello dear world!"--- > ("Hello ","dear world!")----{-# INLINE breakOn #-}-breakOn :: Monad m => Array a -> Fold m a b -> Fold m a c -> Fold m a (b,c)-breakOn pat f m = undefined--}----------------------------------------------------------------------------------- N-ary split on a predicate----------------------------------------------------------------------------------- TODO: Use a Splitter configuration similar to the "split" package to make it--- possible to express all splitting combinations. In general, we can have--- infix/suffix/prefix/condensing of separators, dropping both leading/trailing--- separators. We can have a single split operation taking the splitter config--- as argument.---- | Split on an infixed separator element, dropping the separator. Splits the--- stream on separator elements determined by the supplied predicate, separator--- is considered as infixed between two segments, if one side of the separator--- is missing then it is parsed as an empty stream. The supplied 'Fold' is--- applied on the split segments. With '-' representing non-separator elements--- and '.' as separator, 'splitOn' splits as follows:------ @--- "--.--" => "--" "--"--- "--." => "--" ""--- ".--" => "" "--"--- @------ @splitOn (== x)@ is an inverse of @intercalate (S.yield x)@------ Let's use the following definition for illustration:------ > splitOn' p xs = S.toList $ S.splitOn p (FL.toList) (S.fromList xs)------ >>> splitOn' (== '.') ""--- [""]------ >>> splitOn' (== '.') "."--- ["",""]------ >>> splitOn' (== '.') ".a"--- > ["","a"]------ >>> splitOn' (== '.') "a."--- > ["a",""]------ >>> splitOn' (== '.') "a.b"--- > ["a","b"]------ >>> splitOn' (== '.') "a..b"--- > ["a","","b"]------ @since 0.7.0---- This can be considered as an n-fold version of 'breakOn' where we apply--- 'breakOn' successively on the input stream, dropping the first element--- of the second segment after each break.----{-# INLINE splitOn #-}-splitOn- :: (IsStream t, Monad m)- => (a -> Bool) -> Fold m a b -> t m a -> t m b-splitOn predicate f m =- D.fromStreamD $ D.splitBy predicate f (D.toStreamD m)---- | Like 'splitOn' but the separator is considered as suffixed to the segments--- in the stream. A missing suffix at the end is allowed. A separator at the--- beginning is parsed as empty segment. With '-' representing elements and--- '.' as separator, 'splitOnSuffix' splits as follows:------ @--- "--.--." => "--" "--"--- "--.--" => "--" "--"--- ".--." => "" "--"--- @------ > splitOnSuffix' p xs = S.toList $ S.splitSuffixBy p (FL.toList) (S.fromList xs)------ >>> splitOnSuffix' (== '.') ""--- []------ >>> splitOnSuffix' (== '.') "."--- [""]------ >>> splitOnSuffix' (== '.') "a"--- ["a"]------ >>> splitOnSuffix' (== '.') ".a"--- > ["","a"]------ >>> splitOnSuffix' (== '.') "a."--- > ["a"]------ >>> splitOnSuffix' (== '.') "a.b"--- > ["a","b"]------ >>> splitOnSuffix' (== '.') "a.b."--- > ["a","b"]------ >>> splitOnSuffix' (== '.') "a..b.."--- > ["a","","b",""]------ > lines = splitOnSuffix (== '\n')------ @since 0.7.0---- This can be considered as an n-fold version of 'breakPost' where we apply--- 'breakPost' successively on the input stream, dropping the first element--- of the second segment after each break.----{-# INLINE splitOnSuffix #-}-splitOnSuffix- :: (IsStream t, Monad m)- => (a -> Bool) -> Fold m a b -> t m a -> t m b-splitOnSuffix predicate f m =- D.fromStreamD $ D.splitSuffixBy predicate f (D.toStreamD m)---- | Like 'splitOn' after stripping leading, trailing, and repeated separators.--- Therefore, @".a..b."@ with '.' as the separator would be parsed as--- @["a","b"]@. In other words, its like parsing words from whitespace--- separated text.------ > wordsBy' p xs = S.toList $ S.wordsBy p (FL.toList) (S.fromList xs)------ >>> wordsBy' (== ',') ""--- > []------ >>> wordsBy' (== ',') ","--- > []------ >>> wordsBy' (== ',') ",a,,b,"--- > ["a","b"]------ > words = wordsBy isSpace------ @since 0.7.0---- It is equivalent to splitting in any of the infix/prefix/suffix styles--- followed by removal of empty segments.-{-# INLINE wordsBy #-}-wordsBy- :: (IsStream t, Monad m)- => (a -> Bool) -> Fold m a b -> t m a -> t m b-wordsBy predicate f m =- D.fromStreamD $ D.wordsBy predicate f (D.toStreamD m)---- | Like 'splitOnSuffix' but keeps the suffix attached to the resulting--- splits.------ > splitWithSuffix' p xs = S.toList $ S.splitWithSuffix p (FL.toList) (S.fromList xs)------ >>> splitWithSuffix' (== '.') ""--- []------ >>> splitWithSuffix' (== '.') "."--- ["."]------ >>> splitWithSuffix' (== '.') "a"--- ["a"]------ >>> splitWithSuffix' (== '.') ".a"--- > [".","a"]------ >>> splitWithSuffix' (== '.') "a."--- > ["a."]------ >>> splitWithSuffix' (== '.') "a.b"--- > ["a.","b"]------ >>> splitWithSuffix' (== '.') "a.b."--- > ["a.","b."]------ >>> splitWithSuffix' (== '.') "a..b.."--- > ["a.",".","b.","."]------ @since 0.7.0---- This can be considered as an n-fold version of 'breakPost' where we apply--- 'breakPost' successively on the input stream.----{-# INLINE splitWithSuffix #-}-splitWithSuffix- :: (IsStream t, Monad m)- => (a -> Bool) -> Fold m a b -> t m a -> t m b-splitWithSuffix predicate f m =- D.fromStreamD $ D.splitSuffixBy' predicate f (D.toStreamD m)----------------------------------------------------------------------------------- Split on a delimiter sequence----------------------------------------------------------------------------------- Int list examples for splitOn:------ >>> splitList [] [1,2,3,3,4]--- > [[1],[2],[3],[3],[4]]------ >>> splitList [5] [1,2,3,3,4]--- > [[1,2,3,3,4]]------ >>> splitList [1] [1,2,3,3,4]--- > [[],[2,3,3,4]]------ >>> splitList [4] [1,2,3,3,4]--- > [[1,2,3,3],[]]------ >>> splitList [2] [1,2,3,3,4]--- > [[1],[3,3,4]]------ >>> splitList [3] [1,2,3,3,4]--- > [[1,2],[],[4]]------ >>> splitList [3,3] [1,2,3,3,4]--- > [[1,2],[4]]------ >>> splitList [1,2,3,3,4] [1,2,3,3,4]--- > [[],[]]---- | Like 'splitOn' but the separator is a sequence of elements instead of a--- single element.------ For illustration, let's define a function that operates on pure lists:------ @--- splitOnSeq' pat xs = S.toList $ S.splitOnSeq (A.fromList pat) (FL.toList) (S.fromList xs)--- @------ >>> splitOnSeq' "" "hello"--- > ["h","e","l","l","o"]------ >>> splitOnSeq' "hello" ""--- > [""]------ >>> splitOnSeq' "hello" "hello"--- > ["",""]------ >>> splitOnSeq' "x" "hello"--- > ["hello"]------ >>> splitOnSeq' "h" "hello"--- > ["","ello"]------ >>> splitOnSeq' "o" "hello"--- > ["hell",""]------ >>> splitOnSeq' "e" "hello"--- > ["h","llo"]------ >>> splitOnSeq' "l" "hello"--- > ["he","","o"]------ >>> splitOnSeq' "ll" "hello"--- > ["he","o"]------ 'splitOnSeq' is an inverse of 'intercalate'. The following law always holds:------ > intercalate . splitOn == id------ The following law holds when the separator is non-empty and contains none of--- the elements present in the input lists:------ > splitOn . intercalate == id------ @since 0.7.0---- XXX We can use a polymorphic vector implemented by Array# to represent the--- sequence, that way we can avoid the Storable constraint. If we still need--- Storable Array for performance, we can use a separate splitOnArray API for--- that. We can also have an API where the sequence itself is a lazy stream, so--- that we can search files in files for example.-{-# INLINE splitOnSeq #-}-splitOnSeq- :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)- => Array a -> Fold m a b -> t m a -> t m b-splitOnSeq patt f m = D.fromStreamD $ D.splitOn patt f (D.toStreamD m)--{---- This can be implemented easily using Rabin Karp--- | Split on any one of the given patterns.-{-# INLINE splitOnAny #-}-splitOnAny- :: (IsStream t, Monad m, Storable a, Integral a)- => [Array a] -> Fold m a b -> t m a -> t m b-splitOnAny subseq f m = undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)--}---- | Like 'splitSuffixBy' but the separator is a sequence of elements, instead--- of a predicate for a single element.------ > splitSuffixOn_ pat xs = S.toList $ S.splitSuffixOn (A.fromList pat) (FL.toList) (S.fromList xs)------ >>> splitSuffixOn_ "." ""--- [""]------ >>> splitSuffixOn_ "." "."--- [""]------ >>> splitSuffixOn_ "." "a"--- ["a"]------ >>> splitSuffixOn_ "." ".a"--- > ["","a"]------ >>> splitSuffixOn_ "." "a."--- > ["a"]------ >>> splitSuffixOn_ "." "a.b"--- > ["a","b"]------ >>> splitSuffixOn_ "." "a.b."--- > ["a","b"]------ >>> splitSuffixOn_ "." "a..b.."--- > ["a","","b",""]------ > lines = splitSuffixOn "\n"------ @since 0.7.0-{-# INLINE splitOnSuffixSeq #-}-splitOnSuffixSeq- :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)- => Array a -> Fold m a b -> t m a -> t m b-splitOnSuffixSeq patt f m =- D.fromStreamD $ D.splitSuffixOn False patt f (D.toStreamD m)--{---- | Like 'splitOn' but drops any empty splits.----{-# INLINE wordsOn #-}-wordsOn- :: (IsStream t, Monad m, Storable a, Eq a)- => Array a -> Fold m a b -> t m a -> t m b-wordsOn subseq f m = undefined -- D.fromStreamD $ D.wordsOn f subseq (D.toStreamD m)--}---- XXX use a non-monadic intersperse to remove the MonadAsync constraint.------ | Like 'splitOnSeq' but splits the separator as well, as an infix token.------ > splitOn'_ pat xs = S.toList $ S.splitOn' (A.fromList pat) (FL.toList) (S.fromList xs)------ >>> splitOn'_ "" "hello"--- > ["h","","e","","l","","l","","o"]------ >>> splitOn'_ "hello" ""--- > [""]------ >>> splitOn'_ "hello" "hello"--- > ["","hello",""]------ >>> splitOn'_ "x" "hello"--- > ["hello"]------ >>> splitOn'_ "h" "hello"--- > ["","h","ello"]------ >>> splitOn'_ "o" "hello"--- > ["hell","o",""]------ >>> splitOn'_ "e" "hello"--- > ["h","e","llo"]------ >>> splitOn'_ "l" "hello"--- > ["he","l","","l","o"]------ >>> splitOn'_ "ll" "hello"--- > ["he","ll","o"]------ @since 0.7.0-{-# INLINE splitBySeq #-}-splitBySeq- :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)- => Array a -> Fold m a b -> t m a -> t m b-splitBySeq patt f m =- intersperseM (fold f (A.toStream patt)) $ splitOnSeq patt f m---- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.------ > splitSuffixOn'_ pat xs = S.toList $ FL.splitSuffixOn' (A.fromList pat) (FL.toList) (S.fromList xs)------ >>> splitSuffixOn'_ "." ""--- [""]------ >>> splitSuffixOn'_ "." "."--- ["."]------ >>> splitSuffixOn'_ "." "a"--- ["a"]------ >>> splitSuffixOn'_ "." ".a"--- > [".","a"]------ >>> splitSuffixOn'_ "." "a."--- > ["a."]------ >>> splitSuffixOn'_ "." "a.b"--- > ["a.","b"]------ >>> splitSuffixOn'_ "." "a.b."--- > ["a.","b."]------ >>> splitSuffixOn'_ "." "a..b.."--- > ["a.",".","b.","."]------ @since 0.7.0-{-# INLINE splitWithSuffixSeq #-}-splitWithSuffixSeq- :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)- => Array a -> Fold m a b -> t m a -> t m b-splitWithSuffixSeq patt f m =- D.fromStreamD $ D.splitSuffixOn True patt f (D.toStreamD m)--{---- This can be implemented easily using Rabin Karp--- | Split post any one of the given patterns.-{-# INLINE splitSuffixOnAny #-}-splitSuffixOnAny- :: (IsStream t, Monad m, Storable a, Integral a)- => [Array a] -> Fold m a b -> t m a -> t m b-splitSuffixOnAny subseq f m = undefined- -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)--}----------------------------------------------------------------------------------- Nested Split----------------------------------------------------------------------------------- | Consider a chunked stream of container elements e.g. a stream of @Word8@--- chunked as a stream of arrays of @Word8@. @splitInnerBy splitter joiner--- stream@ splits the inner containers @f a@ using the @splitter@ function and--- joins back the resulting fragments from splitting across multiple containers--- using the @joiner@ function such that the transformed output stream is--- consolidated as one container per segment of the split.------ CAUTION! This is not a true streaming function as the container size after--- the split and merge may not be bounded.------ @since 0.7.0-{-# INLINE splitInnerBy #-}-splitInnerBy- :: (IsStream t, Monad m)- => (f a -> m (f a, Maybe (f a))) -- splitter- -> (f a -> f a -> m (f a)) -- joiner- -> t m (f a)- -> t m (f a)-splitInnerBy splitter joiner xs =- D.fromStreamD $ D.splitInnerBy splitter joiner $ D.toStreamD xs---- | Like 'splitInnerBy' but splits assuming the separator joins the segment in--- a suffix style.------ @since 0.7.0-{-# INLINE splitInnerBySuffix #-}-splitInnerBySuffix- :: (IsStream t, Monad m, Eq (f a), Monoid (f a))- => (f a -> m (f a, Maybe (f a))) -- splitter- -> (f a -> f a -> m (f a)) -- joiner- -> t m (f a)- -> t m (f a)-splitInnerBySuffix splitter joiner xs =- D.fromStreamD $ D.splitInnerBySuffix splitter joiner $ D.toStreamD xs----------------------------------------------------------------------------------- Reorder in sequence---------------------------------------------------------------------------------{---- Buffer until the next element in sequence arrives. The function argument--- determines the difference in sequence numbers. This could be useful in--- implementing sequenced streams, for example, TCP reassembly.-{-# INLINE reassembleBy #-}-reassembleBy- :: (IsStream t, Monad m)- => Fold m a b- -> (a -> a -> Int)- -> t m a- -> t m b-reassembleBy = undefined--}----------------------------------------------------------------------------------- Distributing----------------------------------------------------------------------------------- | Tap the data flowing through a stream into a 'Fold'. For example, you may--- add a tap to log the contents flowing through the stream. The fold is used--- only for effects, its result is discarded.------ @--- Fold m a b--- |--- -----stream m a ---------------stream m a----------- @------ @--- > S.drain $ S.tap (FL.drainBy print) (S.enumerateFromTo 1 2)--- 1--- 2--- @------ Compare with 'trace'.------ @since 0.7.0-tap :: (IsStream t, Monad m) => FL.Fold m a b -> t m a -> t m a-tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)---- | Apply a monadic function to each element flowing through the stream and--- discard the results.------ @--- > S.drain $ S.trace print (S.enumerateFromTo 1 2)--- 1--- 2--- @------ Compare with 'tap'.------ @since 0.7.0-trace :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m a-trace f = mapM (\x -> void (f x) >> return x)----------------------------------------------------------------------------------- Windowed classification----------------------------------------------------------------------------------- We divide the stream into windows or chunks in space or time and each window--- can be associated with a key, all events associated with a particular key in--- the window can be folded to a single result. The stream can be split into--- windows by size or by using a split predicate on the elements in the stream.--- For example, when we receive a closing flag, we can close the window.------ A "chunk" is a space window and a "session" is a time window. Are there any--- other better short words to describe them. An alternative is to use--- "swindow" and "twindow". Another word for "session" could be "spell".------ TODO: To mark the position in space or time we can have Indexed or--- TimeStamped types. That can make it easy to deal with the position indices--- or timestamps.----------------------------------------------------------------------------------- Keyed Sliding Windows---------------------------------------------------------------------------------{--{-# INLINABLE classifySlidingChunks #-}-classifySlidingChunks- :: (IsStream t, MonadAsync m, Ord k)- => Int -- ^ window size- -> Int -- ^ window slide- -> Fold m a b -- ^ Fold to be applied to window events- -> t m (k, a, Bool) -- ^ window key, data, close event- -> t m (k, b)-classifySlidingChunks wsize wslide (Fold step initial extract) str- = undefined---- XXX Another variant could be to slide the window on an event, e.g. in TCP we--- slide the send window when an ack is received and we slide the receive--- window when a sequence is complete. Sliding is stateful in case of TCP,--- sliding releases the send buffer or makes data available to the user from--- the receive buffer.-{-# INLINABLE classifySlidingSessions #-}-classifySlidingSessions- :: (IsStream t, MonadAsync m, Ord k)- => Double -- ^ timer tick in seconds- -> Double -- ^ time window size- -> Double -- ^ window slide- -> Fold m a b -- ^ Fold to be applied to window events- -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp- -> t m (k, b)-classifySlidingSessions tick interval slide (Fold step initial extract) str- = undefined--}----------------------------------------------------------------------------------- Sliding Window Buffers----------------------------------------------------------------------------------- These buffered versions could be faster than concurrent incremental folds of--- all overlapping windows as in many cases we may not need all the values to--- compute the fold, we can just compute the result using the old value and new--- value. However, we may need the buffer once in a while, for example for--- string search we usually compute the hash incrementally but when the hash--- matches the hash of the pattern we need to compare the whole string.------ XXX we should be able to implement sequence based splitting combinators--- using this combinator.--{---- | Buffer n elements of the input in a ring buffer. When t new elements are--- collected, slide the window to remove the same number of oldest elements,--- insert the new elements, and apply an incremental fold on the sliding--- window, supplying the outgoing elements, the new ring buffer as arguments.-slidingChunkBuffer- :: (IsStream t, Monad m, Ord a, Storable a)- => Int -- window size- -> Int -- window slide- -> Fold m (Ring a, Array a) b- -> t m a- -> t m b-slidingChunkBuffer = undefined---- Buffer n seconds worth of stream elements of the input in a radix tree.--- Every t seconds, remove the items that are older than n seconds, and apply--- an incremental fold on the sliding window, supplying the outgoing elements,--- and the new radix tree buffer as arguments.-slidingSessionBuffer- :: (IsStream t, Monad m, Ord a, Storable a)- => Int -- window size- -> Int -- tick size- -> Fold m (RTree a, Array a) b- -> t m a- -> t m b-slidingSessionBuffer = undefined--}----------------------------------------------------------------------------------- Keyed Session Windows---------------------------------------------------------------------------------{---- | Keyed variable size space windows. Close the window if we do not receive a--- window event in the next "spaceout" elements.-{-# INLINABLE classifyChunksBy #-}-classifyChunksBy- :: (IsStream t, MonadAsync m, Ord k)- => Int -- ^ window spaceout (spread)- -> Bool -- ^ reset the spaceout when a chunk window element is received- -> Fold m a b -- ^ Fold to be applied to chunk window elements- -> t m (k, a, Bool) -- ^ chunk key, data, last element- -> t m (k, b)-classifyChunksBy spanout reset (Fold step initial extract) str = undefined---- | Like 'classifyChunksOf' but the chunk size is reset if an element is--- received within the chunk size window. The chunk gets closed only if no--- element is received within the chunk window.----{-# INLINABLE classifyKeepAliveChunks #-}-classifyKeepAliveChunks- :: (IsStream t, MonadAsync m, Ord k)- => Int -- ^ window spaceout (spread)- -> Fold m a b -- ^ Fold to be applied to chunk window elements- -> t m (k, a, Bool) -- ^ chunk key, data, last element- -> t m (k, b)-classifyKeepAliveChunks spanout = classifyChunksBy spanout True--}---- | @classifySessionsBy tick timeout reset f stream@ groups together all input--- stream elements that belong to the same session. @timeout@ is the maximum--- lifetime of a session in seconds. All elements belonging to a session are--- purged after this duration. If "reset" is 'Ture' then the timeout is reset--- after every event received in the session. Session duration is measured--- using the timestamp of the first element seen for that session. To detect--- session timeouts, a monotonic event time clock is maintained using the--- timestamps seen in the inputs and a timer with a tick duration specified by--- @tick@.------ @session key@ is a key that uniquely identifies the session for the given--- element, @timestamp@ characterizes the time when the input element was--- generated, this is an absolute time measured from some @Epoch@. @session--- close@ is a boolean indicating whether this element marks the closing of the--- session. When an input element with @session close@ set to @True@ is seen--- the session is purged immediately.------ All the input elements belonging to a session are collected using the fold--- @f@. The session key and the fold result are emitted in the output stream--- when the session is purged either via the session close event or via the--- session liftime timeout.------ @since 0.7.0-{-# INLINABLE classifySessionsBy #-}-classifySessionsBy- :: (IsStream t, MonadAsync m, Ord k)- => Double -- ^ timer tick in seconds- -> Double -- ^ session timeout- -> Bool -- ^ reset the timeout when an event is received- -> Fold m a b -- ^ Fold to be applied to session events- -> t m (k, a, Bool, AbsTime) -- ^ session key, timestamp, close event, data- -> t m (k, b)-classifySessionsBy tick timeout reset (Fold step initial extract) str =- concatMap (\(Tuple4' _ _ _ s) -> s) $ scanlM' sstep szero stream-- where-- timeoutMs = toRelTime (round (timeout * 1000) :: MilliSecond64)- tickMs = toRelTime (round (tick * 1000) :: MilliSecond64)- szero = Tuple4' (toAbsTime (0 :: MilliSecond64)) H.empty Map.empty K.nil-- -- Got a new stream input element- sstep (Tuple4' evTime hp mp _) (Just (key, a, closing, ts)) =- -- XXX we should use a heap in pinned memory to scale it to a large- -- size- --- -- deleting a key from the heap is expensive, so we never delete a- -- key, we just purge it from the Map and it gets purged from the- -- heap on timeout. We just need an extra lookup in the Map when- -- the key is purged from the heap, that should not be expensive.- --- -- To detect session inactivity we keep a timestamp of the latest event- -- in the Map along with the fold result. When we purge the session- -- from the heap we match the timestamp in the heap with the timestamp- -- in the Map, if the latest timestamp is newer and has not expired we- -- reinsert the key in the heap.- --- -- XXX if the key is an Int, we can also use an IntMap for slightly- -- better performance.- --- let accumulate v = do- Tuple' _ old <- maybe (initial >>= return . Tuple' ts) return v- new <- step old a- return $ Tuple' ts new- in if closing- then do- let (r, mp') = Map.updateLookupWithKey (\_ _ -> Nothing) key mp- Tuple' _ acc <- accumulate r- res <- extract acc- return $ Tuple4' evTime hp mp' (yield (key, res))- else do- let r = Map.lookup key mp- acc <- accumulate r- let mp' = Map.insert key acc mp- let hp' =- case r of- Nothing ->- let expiry = addToAbsTime ts timeoutMs- in H.insert (Entry expiry key) hp- Just _ -> hp- -- Event time is maintained as monotonically increasing- -- time. If we have lagged behind any of the timestamps- -- seen then we increase it to match the latest time seen- -- in the timestamps. We also increase it on timer ticks.- return $ Tuple4' (max evTime ts) hp' mp' K.nil-- -- Got a timer tick event- -- XXX can we yield the entries without accumulating them?- sstep (Tuple4' evTime heap sessions _) Nothing = do- (hp', mp', out) <- go heap sessions K.nil- return $ Tuple4' curTime hp' mp' out-- where-- curTime = addToAbsTime evTime tickMs- go hp mp out = do- let hres = H.uncons hp- case hres of- Just (Entry ts key, hp') -> do- let duration = diffAbsTime curTime ts- if duration >= timeoutMs- then do- let (r, mp') = Map.updateLookupWithKey- (\_ _ -> Nothing) key mp- case r of- Nothing -> go hp' mp' out- Just (Tuple' latestTS acc) -> do- let dur = diffAbsTime curTime latestTS- if dur >= timeoutMs || not reset- then do- sess <- extract acc- go hp' mp' ((key, sess) `K.cons` out)- else- -- reset the session timeout- let expiry = addToAbsTime latestTS timeoutMs- hp'' = H.insert (Entry expiry key) hp'- mp'' = Map.insert key (Tuple' latestTS acc) mp'- in go hp'' mp'' out- else return (hp, mp, out)- Nothing -> return (hp, mp, out)-- -- merge timer events in the stream- stream = Serial.map Just str `Par.parallel` repeatM timer- timer = do- liftIO $ threadDelay (round $ tick * 1000000)- return Nothing---- | Like 'classifySessionsOf' but the session is kept alive if an event is--- received within the session window. The session times out and gets closed--- only if no event is received within the specified session window size.------ @since 0.7.0-{-# INLINABLE classifyKeepAliveSessions #-}-classifyKeepAliveSessions- :: (IsStream t, MonadAsync m, Ord k)- => Double -- ^ session inactive timeout- -> Fold m a b -- ^ Fold to be applied to session payload data- -> t m (k, a, Bool, AbsTime) -- ^ session key, data, close flag, timestamp- -> t m (k, b)-classifyKeepAliveSessions timeout = classifySessionsBy 1 timeout True----------------------------------------------------------------------------------- Keyed tumbling windows----------------------------------------------------------------------------------- Tumbling windows is a special case of sliding windows where the window slide--- is the same as the window size. Or it can be a special case of session--- windows where the reset flag is set to False.---- XXX instead of using the early termination flag in the stream, we can use an--- early terminating fold instead.--{---- | Split the stream into fixed size chunks of specified size. Within each--- such chunk fold the elements in buckets identified by the keys. A particular--- bucket fold can be terminated early if a closing flag is encountered in an--- element for that key.------ @since 0.7.0-{-# INLINABLE classifyChunksOf #-}-classifyChunksOf- :: (IsStream t, MonadAsync m, Ord k)- => Int -- ^ window size- -> Fold m a b -- ^ Fold to be applied to window events- -> t m (k, a, Bool) -- ^ window key, data, close event- -> t m (k, b)-classifyChunksOf wsize = classifyChunksBy wsize False--}---- | Split the stream into fixed size time windows of specified interval in--- seconds. Within each such window, fold the elements in buckets identified by--- the keys. A particular bucket fold can be terminated early if a closing flag--- is encountered in an element for that key. Once a fold is terminated the key--- and value for that bucket are emitted in the output stream.------ Session @timestamp@ in the input stream is an absolute time from some epoch,--- characterizing the time when the input element was generated. To detect--- session window end, a monotonic event time clock is maintained synced with--- the timestamps with a clock resolution of 1 second.------ @since 0.7.0-{-# INLINABLE classifySessionsOf #-}-classifySessionsOf- :: (IsStream t, MonadAsync m, Ord k)- => Double -- ^ time window size- -> Fold m a b -- ^ Fold to be applied to window events- -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp- -> t m (k, b)-classifySessionsOf interval = classifySessionsBy 1 interval False----------------------------------------------------------------------------------- Exceptions----------------------------------------------------------------------------------- | Run a side effect before the stream yields its first element.------ @since 0.7.0-{-# INLINE before #-}-before :: (IsStream t, Monad m) => m b -> t m a -> t m a-before action xs = D.fromStreamD $ D.before action $ D.toStreamD xs---- | Run a side effect whenever the stream stops normally.------ @since 0.7.0-{-# INLINE after #-}-after :: (IsStream t, Monad m) => m b -> t m a -> t m a-after action xs = D.fromStreamD $ D.after action $ D.toStreamD xs---- | Run a side effect whenever the stream aborts due to an exception.------ @since 0.7.0-{-# INLINE onException #-}-onException :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a-onException action xs = D.fromStreamD $ D.onException action $ D.toStreamD xs---- | Run a side effect whenever the stream stops normally or aborts due to an--- exception.------ @since 0.7.0-{-# INLINE finally #-}-finally :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a-finally action xs = D.fromStreamD $ D.finally action $ D.toStreamD xs---- | Run the first action before the stream starts and remember its output,--- generate a stream using the output, run the second action using the--- remembered value as an argument whenever the stream ends normally or due to--- an exception.------ @since 0.7.0-{-# INLINE bracket #-}-bracket :: (IsStream t, MonadCatch m)- => m b -> (b -> m c) -> (b -> t m a) -> t m a-bracket bef aft bet = D.fromStreamD $- D.bracket bef aft (\x -> toStreamD $ bet x)+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}++#if __GLASGOW_HASKELL__ >= 800+{-# OPTIONS_GHC -Wno-orphans #-}+#endif++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Prelude+-- Copyright : (c) 2017 Harendra Kumar+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++module Streamly.Internal.Prelude+ (+ -- * Construction+ -- ** Primitives+ K.nil+ , K.nilM+ , K.cons+ , (K..:)++ , consM+ , (|:)++ -- ** From Values+ , yield+ , yieldM+ , repeat+ , repeatM+ , replicate+ , replicateM++ -- ** Enumeration+ , Enumerable (..)+ , enumerate+ , enumerateTo++ -- ** From Generators+ , unfoldr+ , unfoldrM+ , unfold+ , iterate+ , iterateM+ , fromIndices+ , fromIndicesM++ -- ** From Containers+ , P.fromList+ , fromListM+ , K.fromFoldable+ , fromFoldableM+ , fromPrimVar++ -- ** Time related+ , currentTime++ -- * Elimination++ -- ** Deconstruction+ , uncons+ , tail+ , init++ -- ** Folding+ -- ** Right Folds+ , foldrM+ , foldrS+ , foldrT+ , foldr++ -- ** Left Folds+ , foldl'+ , foldl1'+ , foldlM'++ -- ** Concurrent Folds+ , foldAsync+ , (|$.)+ , (|&.)++ -- ** Full Folds++ -- -- ** To Summary (Full Folds)+ , drain+ , last+ , length+ , sum+ , product+ --, mconcat++ -- -- ** To Summary (Maybe) (Full Folds)+ , maximumBy+ , maximum+ , minimumBy+ , minimum+ , the++ -- ** Partial Folds++ -- -- ** To Elements (Partial Folds)+ , drainN+ , drainWhile++ -- -- | Folds that extract selected elements of a stream or their properties.+ , (!!)+ , head+ , headElse+ , findM+ , find+ , lookup+ , findIndex+ , elemIndex++ -- -- ** To Boolean (Partial Folds)+ , null+ , elem+ , notElem+ , all+ , any+ , and+ , or++ -- ** To Containers+ , toList+ , toListRev+ , toPure+ , toPureRev++ -- ** Composable Left Folds+ , fold++ , toStream -- XXX rename to write?+ , toStreamRev -- XXX rename to writeRev?++ -- * Transformation+ , transform++ -- ** Mapping+ , Serial.map+ , sequence+ , mapM+ , mapM_++ -- ** Scanning+ -- ** Left scans+ , scanl'+ , scanlM'+ , postscanl'+ , postscanlM'+ , prescanl'+ , prescanlM'+ , scanl1'+ , scanl1M'++ -- ** Scan Using Fold+ , scan+ , postscan++ -- , lscanl'+ -- , lscanlM'+ -- , lscanl1'+ -- , lscanl1M'+ --+ -- , lpostscanl'+ -- , lpostscanlM'+ -- , lprescanl'+ -- , lprescanlM'++ -- ** Concurrent Transformation+ , D.mkParallel+ -- Par.mkParallel+ , applyAsync+ , (|$)+ , (|&)++ -- ** Indexing+ , indexed+ , indexedR+ -- , timestamped+ -- , timestampedR -- timer++ -- ** Filtering++ , filter+ , filterM++ -- ** Stateful Filters+ , take+ , takeByTime+ -- , takeEnd+ , takeWhile+ , takeWhileM+ -- , takeWhileEnd+ , drop+ , dropByTime+ -- , dropEnd+ , dropWhile+ , dropWhileM+ -- , dropWhileEnd+ -- , dropAround+ , deleteBy+ , uniq+ -- , uniqBy -- by predicate e.g. to remove duplicate "/" in a path+ -- , uniqOn -- to remove duplicate sequences+ -- , pruneBy -- dropAround + uniqBy - like words++ -- ** Mapping Filters+ , mapMaybe+ , mapMaybeM+ , rollingMapM+ , rollingMap++ -- ** Scanning Filters+ , findIndices+ , elemIndices+ -- , seqIndices -- search a sequence in the stream++ -- ** Insertion+ , insertBy+ , intersperseM+ , intersperse+ , intersperseSuffix+ , intersperseSuffixBySpan+ -- , intersperseBySpan+ , interjectSuffix+ , delayPost++ -- ** Reordering+ , reverse+ , reverse'++ -- * Multi-Stream Operations++ -- ** Appending+ , append++ -- ** Interleaving+ , interleave+ , interleaveMin+ , interleaveSuffix+ , interleaveInfix++ , Serial.wSerialFst+ , Serial.wSerialMin++ -- ** Scheduling+ , roundrobin++ -- ** Parallel+ , Par.parallelFst+ , Par.parallelMin++ -- ** Merging++ -- , merge+ , mergeBy+ , mergeByM+ , mergeAsyncBy+ , mergeAsyncByM++ -- ** Zipping+ , Z.zipWith+ , Z.zipWithM+ , Z.zipAsyncWith+ , Z.zipAsyncWithM++ -- ** Nested Streams+ , concatMapM+ , concatUnfold+ , concatUnfoldInterleave+ , concatUnfoldRoundrobin+ , concatMap+ , concatMapWith+ , gintercalate+ , gintercalateSuffix+ , intercalate+ , intercalateSuffix+ , interpose+ , interposeSuffix+ , concatMapIterateWith+ , concatMapTreeWith+ , concatMapLoopWith+ , concatMapTreeYieldLeavesWith++ -- -- ** Breaking++ -- By chunks+ , splitAt -- spanN+ -- , splitIn -- sessionN++ -- By elements+ , span -- spanWhile+ , break -- breakBefore+ -- , breakAfter+ -- , breakOn+ -- , breakAround+ , spanBy+ , spanByRolling++ -- By sequences+ -- , breakOnSeq++ -- ** Splitting+ -- , groupScan++ -- -- *** Chunks+ , chunksOf+ , chunksOf2+ , arraysOf+ , intervalsOf++ -- -- *** Using Element Separators+ , splitOn+ , splitOnSuffix+ -- , splitOnPrefix++ -- , splitBy+ , splitWithSuffix+ -- , splitByPrefix+ , wordsBy -- stripAndCompactBy++ -- -- *** Using Sequence Separators+ , splitOnSeq+ , splitOnSuffixSeq+ -- , splitOnPrefixSeq++ -- Keeping the delimiters+ , splitBySeq+ , splitWithSuffixSeq+ -- , splitByPrefixSeq+ -- , wordsBySeq++ -- Splitting using multiple sequence separators+ -- , splitOnAnySeq+ -- , splitOnAnySuffixSeq+ -- , splitOnAnyPrefixSeq++ -- Nested splitting+ , splitInnerBy+ , splitInnerBySuffix++ -- ** Grouping+ , groups+ , groupsBy+ , groupsByRolling++ -- ** Distributing+ , trace+ , tap+ , tapOffsetEvery+ , tapAsync+ , tapRate+ , pollCounts++ -- * Windowed Classification++ -- ** Tumbling Windows+ -- , classifyChunksOf+ , classifySessionsBy+ , classifySessionsOf++ -- ** Keep Alive Windows+ -- , classifyKeepAliveChunks+ , classifyKeepAliveSessions++ {-+ -- ** Sliding Windows+ , classifySlidingChunks+ , classifySlidingSessions+ -}+ -- ** Sliding Window Buffers+ -- , slidingChunkBuffer+ -- , slidingSessionBuffer++ -- ** Containers of Streams+ , foldWith+ , foldMapWith+ , forEachWith++ -- ** Folding+ , eqBy+ , cmpBy+ , isPrefixOf+ -- , isSuffixOf+ -- , isInfixOf+ , isSubsequenceOf+ , stripPrefix+ -- , stripSuffix+ -- , stripInfix++ -- * Exceptions+ , before+ , after+ , afterIO+ , bracket+ , bracketIO+ , onException+ , finally+ , finallyIO+ , handle++ -- * Generalize Inner Monad+ , hoist+ , generally++ -- * Transform Inner Monad+ , liftInner+ , runReaderT+ , evalStateT+ , usingStateT+ , runStateT++ -- * MonadFix+ , K.mfix++ -- * Diagnostics+ , inspectMode++ -- * Deprecated+ , K.once+ , each+ , scanx+ , foldx+ , foldxM+ , foldr1+ , runStream+ , runN+ , runWhile+ , fromHandle+ , toHandle+ )+where++import Control.Concurrent (threadDelay)+import Control.Exception (Exception, assert)+import Control.Monad (void)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader (ReaderT)+import Control.Monad.State.Strict (StateT)+import Control.Monad.Trans (MonadTrans(..))+import Control.Monad.Trans.Control (MonadBaseControl)+import Data.Functor.Identity (Identity (..))+#if __GLASGOW_HASKELL__ >= 800+import Data.Kind (Type)+#endif+import Data.Heap (Entry(..))+import Data.Maybe (isJust, fromJust, isNothing)+import Foreign.Storable (Storable)+import Prelude+ hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,+ foldl, map, mapM, mapM_, sequence, all, any, sum, product, elem,+ notElem, maximum, minimum, head, last, tail, length, null,+ reverse, iterate, init, and, or, lookup, foldr1, (!!),+ scanl, scanl1, replicate, concatMap, span, splitAt, break,+ repeat)++import qualified Data.Heap as H+import qualified Data.Map.Strict as Map+import qualified Prelude+import qualified System.IO as IO++import Streamly.Internal.Data.Stream.Enumeration (Enumerable(..), enumerate, enumerateTo)+import Streamly.Internal.Data.Fold.Types (Fold (..), Fold2 (..))+import Streamly.Internal.Data.Unfold.Types (Unfold)+import Streamly.Internal.Memory.Array.Types (Array, writeNUnsafe)+-- import Streamly.Memory.Ring (Ring)+import Streamly.Internal.Data.SVar (MonadAsync, defState)+import Streamly.Internal.Data.Stream.Combinators (inspectMode, maxYields)+import Streamly.Internal.Data.Stream.Prelude+ (fromStreamS, toStreamS, foldWith, foldMapWith, forEachWith)+import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)+import Streamly.Internal.Data.Stream.StreamK (IsStream((|:), consM))+import Streamly.Internal.Data.Stream.Serial (SerialT, WSerialT)+import Streamly.Internal.Data.Stream.Zip (ZipSerialM)+import Streamly.Internal.Data.Pipe.Types (Pipe (..))+import Streamly.Internal.Data.Time.Units+ (AbsTime, MilliSecond64(..), addToAbsTime, toRelTime,+ toAbsTime, TimeUnit64)+import Streamly.Internal.Mutable.Prim.Var (Prim, Var)++import Streamly.Internal.Data.Strict++import qualified Streamly.Internal.Memory.Array as A+import qualified Streamly.Data.Fold as FL+import qualified Streamly.Internal.Data.Fold.Types as FL+import qualified Streamly.Internal.Data.Stream.Prelude as P+import qualified Streamly.Internal.Data.Stream.StreamK as K+import qualified Streamly.Internal.Data.Stream.StreamD as D++#ifdef USE_STREAMK_ONLY+import qualified Streamly.Internal.Data.Stream.StreamK as S+#else+import qualified Streamly.Internal.Data.Stream.StreamD as S+#endif++-- import qualified Streamly.Internal.Data.Stream.Async as Async+import qualified Streamly.Internal.Data.Stream.Serial as Serial+import qualified Streamly.Internal.Data.Stream.Parallel as Par+import qualified Streamly.Internal.Data.Stream.Zip as Z++------------------------------------------------------------------------------+-- Deconstruction+------------------------------------------------------------------------------++-- | Decompose a stream into its head and tail. If the stream is empty, returns+-- 'Nothing'. If the stream is non-empty, returns @Just (a, ma)@, where @a@ is+-- the head of the stream and @ma@ its tail.+--+-- This is a brute force primitive. Avoid using it as long as possible, use it+-- when no other combinator can do the job. This can be used to do pretty much+-- anything in an imperative manner, as it just breaks down the stream into+-- individual elements and we can loop over them as we deem fit. For example,+-- this can be used to convert a streamly stream into other stream types.+--+-- @since 0.1.0+{-# INLINE uncons #-}+uncons :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (a, t m a))+uncons m = K.uncons (K.adapt m)++------------------------------------------------------------------------------+-- Generation by Unfolding+------------------------------------------------------------------------------++-- |+-- @+-- unfoldr step s =+-- case step s of+-- Nothing -> 'K.nil'+-- Just (a, b) -> a \`cons` unfoldr step b+-- @+--+-- Build a stream by unfolding a /pure/ step function @step@ starting from a+-- seed @s@. The step function returns the next element in the stream and the+-- next seed value. When it is done it returns 'Nothing' and the stream ends.+-- For example,+--+-- @+-- let f b =+-- if b > 3+-- then Nothing+-- else Just (b, b + 1)+-- in toList $ unfoldr f 0+-- @+-- @+-- [0,1,2,3]+-- @+--+-- @since 0.1.0+{-# INLINE_EARLY unfoldr #-}+unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a+unfoldr step seed = fromStreamS (S.unfoldr step seed)+{-# RULES "unfoldr fallback to StreamK" [1]+ forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}++-- | Build a stream by unfolding a /monadic/ step function starting from a+-- seed. The step function returns the next element in the stream and the next+-- seed value. When it is done it returns 'Nothing' and the stream ends. For+-- example,+--+-- @+-- let f b =+-- if b > 3+-- then return Nothing+-- else print b >> return (Just (b, b + 1))+-- in drain $ unfoldrM f 0+-- @+-- @+-- 0+-- 1+-- 2+-- 3+-- @+-- When run concurrently, the next unfold step can run concurrently with the+-- processing of the output of the previous step. Note that more than one step+-- cannot run concurrently as the next step depends on the output of the+-- previous step.+--+-- @+-- (asyncly $ S.unfoldrM (\\n -> liftIO (threadDelay 1000000) >> return (Just (n, n + 1))) 0)+-- & S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()+-- @+--+-- /Concurrent/+--+-- /Since: 0.1.0/+{-# INLINE_EARLY unfoldrM #-}+unfoldrM :: (IsStream t, MonadAsync m) => (b -> m (Maybe (a, b))) -> b -> t m a+unfoldrM = K.unfoldrM++{-# RULES "unfoldrM serial" unfoldrM = unfoldrMSerial #-}+{-# INLINE_EARLY unfoldrMSerial #-}+unfoldrMSerial :: MonadAsync m => (b -> m (Maybe (a, b))) -> b -> SerialT m a+unfoldrMSerial = Serial.unfoldrM++{-# RULES "unfoldrM wSerial" unfoldrM = unfoldrMWSerial #-}+{-# INLINE_EARLY unfoldrMWSerial #-}+unfoldrMWSerial :: MonadAsync m => (b -> m (Maybe (a, b))) -> b -> WSerialT m a+unfoldrMWSerial = Serial.unfoldrM++{-# RULES "unfoldrM zipSerial" unfoldrM = unfoldrMZipSerial #-}+{-# INLINE_EARLY unfoldrMZipSerial #-}+unfoldrMZipSerial :: MonadAsync m => (b -> m (Maybe (a, b))) -> b -> ZipSerialM m a+unfoldrMZipSerial = Serial.unfoldrM++-- | Convert an 'Unfold' into a stream by supplying it an input seed.+--+-- >>> unfold (UF.replicateM 10) (putStrLn "hello")+--+-- /Since: 0.7.0/+{-# INLINE unfold #-}+unfold :: (IsStream t, Monad m) => Unfold m a b -> a -> t m b+unfold unf x = fromStreamD $ D.unfold unf x++------------------------------------------------------------------------------+-- Specialized Generation+------------------------------------------------------------------------------++-- Faster than yieldM because there is no bind.+--+-- |+-- @+-- yield a = a \`cons` nil+-- @+--+-- Create a singleton stream from a pure value.+--+-- The following holds in monadic streams, but not in Zip streams:+--+-- @+-- yield = pure+-- yield = yieldM . pure+-- @+--+-- In Zip applicative streams 'yield' is not the same as 'pure' because in that+-- case 'pure' is equivalent to 'repeat' instead. 'yield' and 'pure' are+-- equally efficient, in other cases 'yield' may be slightly more efficient+-- than the other equivalent definitions.+--+-- @since 0.4.0+{-# INLINE yield #-}+yield :: IsStream t => a -> t m a+yield = K.yield++-- |+-- @+-- yieldM m = m \`consM` nil+-- @+--+-- Create a singleton stream from a monadic action.+--+-- @+-- > toList $ yieldM getLine+-- hello+-- ["hello"]+-- @+--+-- @since 0.4.0+{-# INLINE yieldM #-}+yieldM :: (Monad m, IsStream t) => m a -> t m a+yieldM = K.yieldM++-- |+-- @+-- fromIndices f = let g i = f i \`cons` g (i + 1) in g 0+-- @+--+-- Generate an infinite stream, whose values are the output of a function @f@+-- applied on the corresponding index. Index starts at 0.+--+-- @+-- > S.toList $ S.take 5 $ S.fromIndices id+-- [0,1,2,3,4]+-- @+--+-- @since 0.6.0+{-# INLINE fromIndices #-}+fromIndices :: (IsStream t, Monad m) => (Int -> a) -> t m a+fromIndices = fromStreamS . S.fromIndices++--+-- |+-- @+-- fromIndicesM f = let g i = f i \`consM` g (i + 1) in g 0+-- @+--+-- Generate an infinite stream, whose values are the output of a monadic+-- function @f@ applied on the corresponding index. Index starts at 0.+--+-- /Concurrent/+--+-- @since 0.6.0+{-# INLINE_EARLY fromIndicesM #-}+fromIndicesM :: (IsStream t, MonadAsync m) => (Int -> m a) -> t m a+fromIndicesM = K.fromIndicesM++{-# RULES "fromIndicesM serial" fromIndicesM = fromIndicesMSerial #-}+{-# INLINE fromIndicesMSerial #-}+fromIndicesMSerial :: MonadAsync m => (Int -> m a) -> SerialT m a+fromIndicesMSerial = fromStreamS . S.fromIndicesM++-- |+-- @+-- replicateM = take n . repeatM+-- @+--+-- Generate a stream by performing a monadic action @n@ times. Same as:+--+-- @+-- drain $ serially $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)+-- drain $ asyncly $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)+-- @+--+-- /Concurrent/+--+-- @since 0.1.1+{-# INLINE_EARLY replicateM #-}+replicateM :: (IsStream t, MonadAsync m) => Int -> m a -> t m a+replicateM = K.replicateM++{-# RULES "replicateM serial" replicateM = replicateMSerial #-}+{-# INLINE replicateMSerial #-}+replicateMSerial :: MonadAsync m => Int -> m a -> SerialT m a+replicateMSerial n = fromStreamS . S.replicateM n++-- |+-- @+-- replicate = take n . repeat+-- @+--+-- Generate a stream of length @n@ by repeating a value @n@ times.+--+-- @since 0.6.0+{-# INLINE_NORMAL replicate #-}+replicate :: (IsStream t, Monad m) => Int -> a -> t m a+replicate n = fromStreamS . S.replicate n++-- |+-- Generate an infinite stream by repeating a pure value.+--+-- @since 0.4.0+{-# INLINE_NORMAL repeat #-}+repeat :: (IsStream t, Monad m) => a -> t m a+repeat = fromStreamS . S.repeat++-- |+-- @+-- repeatM = fix . consM+-- repeatM = cycle1 . yieldM+-- @+--+-- Generate a stream by repeatedly executing a monadic action forever.+--+-- @+-- drain $ serially $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)+-- drain $ asyncly $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)+-- @+--+-- /Concurrent, infinite (do not use with 'parallely')/+--+-- @since 0.2.0+{-# INLINE_EARLY repeatM #-}+repeatM :: (IsStream t, MonadAsync m) => m a -> t m a+repeatM = K.repeatM++{-# RULES "repeatM serial" repeatM = repeatMSerial #-}+{-# INLINE repeatMSerial #-}+repeatMSerial :: MonadAsync m => m a -> SerialT m a+repeatMSerial = fromStreamS . S.repeatM++-- |+-- @+-- iterate f x = x \`cons` iterate f x+-- @+--+-- Generate an infinite stream with @x@ as the first element and each+-- successive element derived by applying the function @f@ on the previous+-- element.+--+-- @+-- > S.toList $ S.take 5 $ S.iterate (+1) 1+-- [1,2,3,4,5]+-- @+--+-- @since 0.1.2+{-# INLINE_NORMAL iterate #-}+iterate :: (IsStream t, Monad m) => (a -> a) -> a -> t m a+iterate step = fromStreamS . S.iterate step++-- |+-- @+-- iterateM f m = m >>= \a -> return a \`consM` iterateM f (f a)+-- @+--+-- Generate an infinite stream with the first element generated by the action+-- @m@ and each successive element derived by applying the monadic function+-- @f@ on the previous element.+--+-- When run concurrently, the next iteration can run concurrently with the+-- processing of the previous iteration. Note that more than one iteration+-- cannot run concurrently as the next iteration depends on the output of the+-- previous iteration.+--+-- @+-- drain $ serially $ S.take 10 $ S.iterateM+-- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) (return 0)+--+-- drain $ asyncly $ S.take 10 $ S.iterateM+-- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) (return 0)+-- @+--+-- /Concurrent/+--+-- /Since: 0.7.0 (signature change)/+--+-- /Since: 0.1.2/+{-# INLINE_EARLY iterateM #-}+iterateM :: (IsStream t, MonadAsync m) => (a -> m a) -> m a -> t m a+iterateM = K.iterateM++{-# RULES "iterateM serial" iterateM = iterateMSerial #-}+{-# INLINE iterateMSerial #-}+iterateMSerial :: MonadAsync m => (a -> m a) -> m a -> SerialT m a+iterateMSerial step = fromStreamS . S.iterateM step++------------------------------------------------------------------------------+-- Conversions+------------------------------------------------------------------------------++-- |+-- @+-- fromListM = 'Prelude.foldr' 'K.consM' 'K.nil'+-- @+--+-- Construct a stream from a list of monadic actions. This is more efficient+-- than 'fromFoldableM' for serial streams.+--+-- @since 0.4.0+{-# INLINE_EARLY fromListM #-}+fromListM :: (MonadAsync m, IsStream t) => [m a] -> t m a+fromListM = fromStreamD . D.fromListM+{-# RULES "fromListM fallback to StreamK" [1]+ forall a. D.toStreamK (D.fromListM a) = fromFoldableM a #-}++-- |+-- @+-- fromFoldableM = 'Prelude.foldr' 'consM' 'K.nil'+-- @+--+-- Construct a stream from a 'Foldable' containing monadic actions.+--+-- @+-- drain $ serially $ S.fromFoldableM $ replicateM 10 (threadDelay 1000000 >> print 1)+-- drain $ asyncly $ S.fromFoldableM $ replicateM 10 (threadDelay 1000000 >> print 1)+-- @+--+-- /Concurrent (do not use with 'parallely' on infinite containers)/+--+-- @since 0.3.0+{-# INLINE fromFoldableM #-}+fromFoldableM :: (IsStream t, MonadAsync m, Foldable f) => f (m a) -> t m a+fromFoldableM = Prelude.foldr consM K.nil++-- | Same as 'fromFoldable'.+--+-- @since 0.1.0+{-# DEPRECATED each "Please use fromFoldable instead." #-}+{-# INLINE each #-}+each :: (IsStream t, Foldable f) => f a -> t m a+each = K.fromFoldable++-- | Read lines from an IO Handle into a stream of Strings.+--+-- @since 0.1.0+{-# DEPRECATED fromHandle+ "Please use Streamly.FileSystem.Handle module (see the changelog)" #-}+fromHandle :: (IsStream t, MonadIO m) => IO.Handle -> t m String+fromHandle h = go+ where+ go = K.mkStream $ \_ yld _ stp -> do+ eof <- liftIO $ IO.hIsEOF h+ if eof+ then stp+ else do+ str <- liftIO $ IO.hGetLine h+ yld str go++-- | Construct a stream by reading a 'Prim' 'Var' repeatedly.+--+-- /Internal/+--+{-# INLINE fromPrimVar #-}+fromPrimVar :: (IsStream t, MonadIO m, Prim a) => Var IO a -> t m a+fromPrimVar = fromStreamD . D.fromPrimVar++------------------------------------------------------------------------------+-- Time related+------------------------------------------------------------------------------++-- XXX Some related/interesting combinators:+--+-- 1) emit the relative time elapsed since last evaluation. That would just be+-- a rollingMap on the currentTime stream.+--+-- 2) Generate ticks at specified interval. Drop ticks when blocked.+-- ticks :: Double -> t m ()+--+-- 3) Emit relative time at specified tick interval. If a tick is dropped+-- combine the interval with the next tick.+-- ticks :: Double -> t m RelTime+--+-- | @currentTime g@ returns a stream of absolute timestamps using a clock of+-- granularity @g@ specified in seconds. A low granularity clock is more+-- expensive in terms of CPU usage.+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Internal/+--+{-# INLINE currentTime #-}+currentTime :: (IsStream t, MonadAsync m) => Double -> t m AbsTime+currentTime g = fromStreamD $ D.currentTime g++------------------------------------------------------------------------------+-- Elimination by Folding+------------------------------------------------------------------------------++-- | Right associative/lazy pull fold. @foldrM build final stream@ constructs+-- an output structure using the step function @build@. @build@ is invoked with+-- the next input element and the remaining (lazy) tail of the output+-- structure. It builds a lazy output expression using the two. When the "tail+-- structure" in the output expression is evaluated it calls @build@ again thus+-- lazily consuming the input @stream@ until either the output expression built+-- by @build@ is free of the "tail" or the input is exhausted in which case+-- @final@ is used as the terminating case for the output structure. For more+-- details see the description in the previous section.+--+-- Example, determine if any element is 'odd' in a stream:+--+-- >>> S.foldrM (\x xs -> if odd x then return True else xs) (return False) $ S.fromList (2:4:5:undefined)+-- > True+--+-- /Since: 0.7.0 (signature changed)/+--+-- /Since: 0.2.0 (signature changed)/+--+-- /Since: 0.1.0/+{-# INLINE foldrM #-}+foldrM :: Monad m => (a -> m b -> m b) -> m b -> SerialT m a -> m b+foldrM = P.foldrM++-- | Right fold to a streaming monad.+--+-- > foldrS S.cons S.nil === id+--+-- 'foldrS' can be used to perform stateless stream to stream transformations+-- like map and filter in general. It can be coupled with a scan to perform+-- stateful transformations. However, note that the custom map and filter+-- routines can be much more efficient than this due to better stream fusion.+--+-- >>> S.toList $ S.foldrS S.cons S.nil $ S.fromList [1..5]+-- > [1,2,3,4,5]+--+-- Find if any element in the stream is 'True':+--+-- >>> S.toList $ S.foldrS (\x xs -> if odd x then return True else xs) (return False) $ (S.fromList (2:4:5:undefined) :: SerialT IO Int)+-- > [True]+--+-- Map (+2) on odd elements and filter out the even elements:+--+-- >>> S.toList $ S.foldrS (\x xs -> if odd x then (x + 2) `S.cons` xs else xs) S.nil $ (S.fromList [1..5] :: SerialT IO Int)+-- > [3,5,7]+--+-- 'foldrM' can also be represented in terms of 'foldrS', however, the former+-- is much more efficient:+--+-- > foldrM f z s = runIdentityT $ foldrS (\x xs -> lift $ f x (runIdentityT xs)) (lift z) s+--+-- @since 0.7.0+{-# INLINE foldrS #-}+foldrS :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b+foldrS = K.foldrS++-- | Right fold to a transformer monad. This is the most general right fold+-- function. 'foldrS' is a special case of 'foldrT', however 'foldrS'+-- implementation can be more efficient:+--+-- > foldrS = foldrT+-- > foldrM f z s = runIdentityT $ foldrT (\x xs -> lift $ f x (runIdentityT xs)) (lift z) s+--+-- 'foldrT' can be used to translate streamly streams to other transformer+-- monads e.g. to a different streaming type.+--+-- @since 0.7.0+{-# INLINE foldrT #-}+foldrT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)+ => (a -> s m b -> s m b) -> s m b -> t m a -> s m b+foldrT f z s = S.foldrT f z (toStreamS s)++-- | Right fold, lazy for lazy monads and pure streams, and strict for strict+-- monads.+--+-- Please avoid using this routine in strict monads like IO unless you need a+-- strict right fold. This is provided only for use in lazy monads (e.g.+-- Identity) or pure streams. Note that with this signature it is not possible+-- to implement a lazy foldr when the monad @m@ is strict. In that case it+-- would be strict in its accumulator and therefore would necessarily consume+-- all its input.+--+-- @since 0.1.0+{-# INLINE foldr #-}+foldr :: Monad m => (a -> b -> b) -> b -> SerialT m a -> m b+foldr = P.foldr++-- XXX This seems to be of limited use as it cannot be used to construct+-- recursive structures and for reduction foldl1' is better.+--+-- | Lazy right fold for non-empty streams, using first element as the starting+-- value. Returns 'Nothing' if the stream is empty.+--+-- @since 0.5.0+{-# INLINE foldr1 #-}+{-# DEPRECATED foldr1 "Use foldrM instead." #-}+foldr1 :: Monad m => (a -> a -> a) -> SerialT m a -> m (Maybe a)+foldr1 f m = S.foldr1 f (toStreamS m)++-- | Strict left fold with an extraction function. Like the standard strict+-- left fold, but applies a user supplied extraction function (the third+-- argument) to the folded value at the end. This is designed to work with the+-- @foldl@ library. The suffix @x@ is a mnemonic for extraction.+--+-- @since 0.2.0+{-# DEPRECATED foldx "Please use foldl' followed by fmap instead." #-}+{-# INLINE foldx #-}+foldx :: Monad m => (x -> a -> x) -> x -> (x -> b) -> SerialT m a -> m b+foldx = P.foldlx'++-- | Left associative/strict push fold. @foldl' reduce initial stream@ invokes+-- @reduce@ with the accumulator and the next input in the input stream, using+-- @initial@ as the initial value of the current value of the accumulator. When+-- the input is exhausted the current value of the accumulator is returned.+-- Make sure to use a strict data structure for accumulator to not build+-- unnecessary lazy expressions unless that's what you want. See the previous+-- section for more details.+--+-- @since 0.2.0+{-# INLINE foldl' #-}+foldl' :: Monad m => (b -> a -> b) -> b -> SerialT m a -> m b+foldl' = P.foldl'++-- | Strict left fold, for non-empty streams, using first element as the+-- starting value. Returns 'Nothing' if the stream is empty.+--+-- @since 0.5.0+{-# INLINE foldl1' #-}+foldl1' :: Monad m => (a -> a -> a) -> SerialT m a -> m (Maybe a)+foldl1' step m = do+ r <- uncons m+ case r of+ Nothing -> return Nothing+ Just (h, t) -> do+ res <- foldl' step h t+ return $ Just res++-- | Like 'foldx', but with a monadic step function.+--+-- @since 0.2.0+{-# DEPRECATED foldxM "Please use foldlM' followed by fmap instead." #-}+{-# INLINE foldxM #-}+foldxM :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> SerialT m a -> m b+foldxM = P.foldlMx'++-- | Like 'foldl'' but with a monadic step function.+--+-- @since 0.2.0+{-# INLINE foldlM' #-}+foldlM' :: Monad m => (b -> a -> m b) -> b -> SerialT m a -> m b+foldlM' step begin m = S.foldlM' step begin $ toStreamS m++------------------------------------------------------------------------------+-- Running a Fold+------------------------------------------------------------------------------++-- | Fold a stream using the supplied left fold.+--+-- >>> S.fold FL.sum (S.enumerateFromTo 1 100)+-- 5050+--+-- @since 0.7.0+{-# INLINE fold #-}+fold :: Monad m => Fold m a b -> SerialT m a -> m b+fold = P.runFold++------------------------------------------------------------------------------+-- Running a sink+------------------------------------------------------------------------------++{-+-- | Drain a stream to a 'Sink'.+{-# INLINE runSink #-}+runSink :: Monad m => Sink m a -> SerialT m a -> m ()+runSink = fold . toFold+-}++------------------------------------------------------------------------------+-- Specialized folds+------------------------------------------------------------------------------++-- |+-- > drain = mapM_ (\_ -> return ())+--+-- Run a stream, discarding the results. By default it interprets the stream+-- as 'SerialT', to run other types of streams use the type adapting+-- combinators for example @drain . 'asyncly'@.+--+-- @since 0.7.0+{-# INLINE drain #-}+drain :: Monad m => SerialT m a -> m ()+drain = P.drain++-- | Run a stream, discarding the results. By default it interprets the stream+-- as 'SerialT', to run other types of streams use the type adapting+-- combinators for example @runStream . 'asyncly'@.+--+-- @since 0.2.0+{-# DEPRECATED runStream "Please use \"drain\" instead" #-}+{-# INLINE runStream #-}+runStream :: Monad m => SerialT m a -> m ()+runStream = drain++-- |+-- > drainN n = drain . take n+--+-- Run maximum up to @n@ iterations of a stream.+--+-- @since 0.7.0+{-# INLINE drainN #-}+drainN :: Monad m => Int -> SerialT m a -> m ()+drainN n = drain . take n++-- |+-- > runN n = runStream . take n+--+-- Run maximum up to @n@ iterations of a stream.+--+-- @since 0.6.0+{-# DEPRECATED runN "Please use \"drainN\" instead" #-}+{-# INLINE runN #-}+runN :: Monad m => Int -> SerialT m a -> m ()+runN = drainN++-- |+-- > drainWhile p = drain . takeWhile p+--+-- Run a stream as long as the predicate holds true.+--+-- @since 0.7.0+{-# INLINE drainWhile #-}+drainWhile :: Monad m => (a -> Bool) -> SerialT m a -> m ()+drainWhile p = drain . takeWhile p++-- |+-- > runWhile p = runStream . takeWhile p+--+-- Run a stream as long as the predicate holds true.+--+-- @since 0.6.0+{-# DEPRECATED runWhile "Please use \"drainWhile\" instead" #-}+{-# INLINE runWhile #-}+runWhile :: Monad m => (a -> Bool) -> SerialT m a -> m ()+runWhile = drainWhile++-- | Determine whether the stream is empty.+--+-- @since 0.1.1+{-# INLINE null #-}+null :: Monad m => SerialT m a -> m Bool+null = S.null . toStreamS++-- | Extract the first element of the stream, if any.+--+-- > head = (!! 0)+--+-- @since 0.1.0+{-# INLINE head #-}+head :: Monad m => SerialT m a -> m (Maybe a)+head = S.head . toStreamS++-- | Extract the first element of the stream, if any, otherwise use the+-- supplied default value. It can help avoid one branch in high performance+-- code.+--+-- /Internal/+{-# INLINE headElse #-}+headElse :: Monad m => a -> SerialT m a -> m a+headElse x = D.headElse x . toStreamD++-- |+-- > tail = fmap (fmap snd) . uncons+--+-- Extract all but the first element of the stream, if any.+--+-- @since 0.1.1+{-# INLINE tail #-}+tail :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (t m a))+tail m = K.tail (K.adapt m)++-- | Extract all but the last element of the stream, if any.+--+-- @since 0.5.0+{-# INLINE init #-}+init :: (IsStream t, Monad m) => SerialT m a -> m (Maybe (t m a))+init m = K.init (K.adapt m)++-- | Extract the last element of the stream, if any.+--+-- > last xs = xs !! (length xs - 1)+--+-- @since 0.1.1+{-# INLINE last #-}+last :: Monad m => SerialT m a -> m (Maybe a)+last m = S.last $ toStreamS m++-- | Determine whether an element is present in the stream.+--+-- @since 0.1.0+{-# INLINE elem #-}+elem :: (Monad m, Eq a) => a -> SerialT m a -> m Bool+elem e m = S.elem e (toStreamS m)++-- | Determine whether an element is not present in the stream.+--+-- @since 0.1.0+{-# INLINE notElem #-}+notElem :: (Monad m, Eq a) => a -> SerialT m a -> m Bool+notElem e m = S.notElem e (toStreamS m)++-- | Determine the length of the stream.+--+-- @since 0.1.0+{-# INLINE length #-}+length :: Monad m => SerialT m a -> m Int+length = foldl' (\n _ -> n + 1) 0++-- | Determine whether all elements of a stream satisfy a predicate.+--+-- @since 0.1.0+{-# INLINE all #-}+all :: Monad m => (a -> Bool) -> SerialT m a -> m Bool+all p m = S.all p (toStreamS m)++-- | Determine whether any of the elements of a stream satisfy a predicate.+--+-- @since 0.1.0+{-# INLINE any #-}+any :: Monad m => (a -> Bool) -> SerialT m a -> m Bool+any p m = S.any p (toStreamS m)++-- | Determines if all elements of a boolean stream are True.+--+-- @since 0.5.0+{-# INLINE and #-}+and :: Monad m => SerialT m Bool -> m Bool+and = all (==True)++-- | Determines whether at least one element of a boolean stream is True.+--+-- @since 0.5.0+{-# INLINE or #-}+or :: Monad m => SerialT m Bool -> m Bool+or = any (==True)++-- | Determine the sum of all elements of a stream of numbers. Returns @0@ when+-- the stream is empty. Note that this is not numerically stable for floating+-- point numbers.+--+-- @since 0.1.0+{-# INLINE sum #-}+sum :: (Monad m, Num a) => SerialT m a -> m a+sum = foldl' (+) 0++-- | Determine the product of all elements of a stream of numbers. Returns @1@+-- when the stream is empty.+--+-- @since 0.1.1+{-# INLINE product #-}+product :: (Monad m, Num a) => SerialT m a -> m a+product = foldl' (*) 1++-- |+-- @+-- minimum = 'minimumBy' compare+-- @+--+-- Determine the minimum element in a stream.+--+-- @since 0.1.0+{-# INLINE minimum #-}+minimum :: (Monad m, Ord a) => SerialT m a -> m (Maybe a)+minimum m = S.minimum (toStreamS m)++-- | Determine the minimum element in a stream using the supplied comparison+-- function.+--+-- @since 0.6.0+{-# INLINE minimumBy #-}+minimumBy :: Monad m => (a -> a -> Ordering) -> SerialT m a -> m (Maybe a)+minimumBy cmp m = S.minimumBy cmp (toStreamS m)++-- |+-- @+-- maximum = 'maximumBy' compare+-- @+--+-- Determine the maximum element in a stream.+--+-- @since 0.1.0+{-# INLINE maximum #-}+maximum :: (Monad m, Ord a) => SerialT m a -> m (Maybe a)+maximum = P.maximum++-- | Determine the maximum element in a stream using the supplied comparison+-- function.+--+-- @since 0.6.0+{-# INLINE maximumBy #-}+maximumBy :: Monad m => (a -> a -> Ordering) -> SerialT m a -> m (Maybe a)+maximumBy cmp m = S.maximumBy cmp (toStreamS m)++-- | Lookup the element at the given index.+--+-- @since 0.6.0+{-# INLINE (!!) #-}+(!!) :: Monad m => SerialT m a -> Int -> m (Maybe a)+m !! i = toStreamS m S.!! i++-- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the+-- first pair where the key equals the given value @a@.+--+-- > lookup = snd <$> find ((==) . fst)+--+-- @since 0.5.0+{-# INLINE lookup #-}+lookup :: (Monad m, Eq a) => a -> SerialT m (a, b) -> m (Maybe b)+lookup a m = S.lookup a (toStreamS m)++-- | Like 'findM' but with a non-monadic predicate.+--+-- > find p = findM (return . p)+--+-- @since 0.5.0+{-# INLINE find #-}+find :: Monad m => (a -> Bool) -> SerialT m a -> m (Maybe a)+find p m = S.find p (toStreamS m)++-- | Returns the first element that satisfies the given predicate.+--+-- @since 0.6.0+{-# INLINE findM #-}+findM :: Monad m => (a -> m Bool) -> SerialT m a -> m (Maybe a)+findM p m = S.findM p (toStreamS m)++-- | Find all the indices where the element in the stream satisfies the given+-- predicate.+--+-- @since 0.5.0+{-# INLINE findIndices #-}+findIndices :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m Int+findIndices p m = fromStreamS $ S.findIndices p (toStreamS m)++-- | Returns the first index that satisfies the given predicate.+--+-- @since 0.5.0+{-# INLINE findIndex #-}+findIndex :: Monad m => (a -> Bool) -> SerialT m a -> m (Maybe Int)+findIndex p = head . findIndices p++-- | Find all the indices where the value of the element in the stream is equal+-- to the given value.+--+-- @since 0.5.0+{-# INLINE elemIndices #-}+elemIndices :: (IsStream t, Eq a, Monad m) => a -> t m a -> t m Int+elemIndices a = findIndices (==a)++-- | Returns the first index where a given value is found in the stream.+--+-- > elemIndex a = findIndex (== a)+--+-- @since 0.5.0+{-# INLINE elemIndex #-}+elemIndex :: (Monad m, Eq a) => a -> SerialT m a -> m (Maybe Int)+elemIndex a = findIndex (== a)++------------------------------------------------------------------------------+-- Substreams+------------------------------------------------------------------------------++-- | Returns 'True' if the first stream is the same as or a prefix of the+-- second. A stream is a prefix of itself.+--+-- @+-- > S.isPrefixOf (S.fromList "hello") (S.fromList "hello" :: SerialT IO Char)+-- True+-- @+--+-- @since 0.6.0+{-# INLINE isPrefixOf #-}+isPrefixOf :: (Eq a, IsStream t, Monad m) => t m a -> t m a -> m Bool+isPrefixOf m1 m2 = D.isPrefixOf (toStreamD m1) (toStreamD m2)++-- | Returns 'True' if all the elements of the first stream occur, in order, in+-- the second stream. The elements do not have to occur consecutively. A stream+-- is a subsequence of itself.+--+-- @+-- > S.isSubsequenceOf (S.fromList "hlo") (S.fromList "hello" :: SerialT IO Char)+-- True+-- @+--+-- @since 0.6.0+{-# INLINE isSubsequenceOf #-}+isSubsequenceOf :: (Eq a, IsStream t, Monad m) => t m a -> t m a -> m Bool+isSubsequenceOf m1 m2 = D.isSubsequenceOf (toStreamD m1) (toStreamD m2)++-- | Drops the given prefix from a stream. Returns 'Nothing' if the stream does+-- not start with the given prefix. Returns @Just nil@ when the prefix is the+-- same as the stream.+--+-- @since 0.6.0+{-# INLINE stripPrefix #-}+stripPrefix+ :: (Eq a, IsStream t, Monad m)+ => t m a -> t m a -> m (Maybe (t m a))+stripPrefix m1 m2 = fmap fromStreamD <$>+ D.stripPrefix (toStreamD m1) (toStreamD m2)++------------------------------------------------------------------------------+-- Map and Fold+------------------------------------------------------------------------------++-- XXX this can utilize parallel mapping if we implement it as drain . mapM+-- |+-- > mapM_ = drain . mapM+--+-- Apply a monadic action to each element of the stream and discard the output+-- of the action. This is not really a pure transformation operation but a+-- transformation followed by fold.+--+-- @since 0.1.0+{-# INLINE mapM_ #-}+mapM_ :: Monad m => (a -> m b) -> SerialT m a -> m ()+mapM_ f m = S.mapM_ f $ toStreamS m++------------------------------------------------------------------------------+-- Conversions+------------------------------------------------------------------------------++-- |+-- @+-- toList = S.foldr (:) []+-- @+--+-- Convert a stream into a list in the underlying monad. The list can be+-- consumed lazily in a lazy monad (e.g. 'Identity'). In a strict monad (e.g.+-- IO) the whole list is generated and buffered before it can be consumed.+--+-- /Warning!/ working on large lists accumulated as buffers in memory could be+-- very inefficient, consider using "Streamly.Array" instead.+--+-- @since 0.1.0+{-# INLINE toList #-}+toList :: Monad m => SerialT m a -> m [a]+toList = P.toList++-- |+-- @+-- toListRev = S.foldl' (flip (:)) []+-- @+--+-- Convert a stream into a list in reverse order in the underlying monad.+--+-- /Warning!/ working on large lists accumulated as buffers in memory could be+-- very inefficient, consider using "Streamly.Array" instead.+--+-- /Internal/+{-# INLINE toListRev #-}+toListRev :: Monad m => SerialT m a -> m [a]+toListRev = D.toListRev . toStreamD++-- |+-- @+-- toHandle h = S.mapM_ $ hPutStrLn h+-- @+--+-- Write a stream of Strings to an IO Handle.+--+-- @since 0.1.0+{-# DEPRECATED toHandle+ "Please use Streamly.FileSystem.Handle module (see the changelog)" #-}+toHandle :: MonadIO m => IO.Handle -> SerialT m String -> m ()+toHandle h m = go m+ where+ go m1 =+ let stop = return ()+ single a = liftIO (IO.hPutStrLn h a)+ yieldk a r = liftIO (IO.hPutStrLn h a) >> go r+ in K.foldStream defState yieldk single stop m1++-- XXX rename these to write/writeRev to make the naming consistent with folds+-- in other modules.+--+-- | A fold that buffers its input to a pure stream.+--+-- /Warning!/ working on large streams accumulated as buffers in memory could+-- be very inefficient, consider using "Streamly.Array" instead.+--+-- /Internal/+{-# INLINE toStream #-}+toStream :: Monad m => Fold m a (SerialT Identity a)+toStream = Fold (\f x -> return $ f . (x `K.cons`))+ (return id)+ (return . ($ K.nil))++-- This is more efficient than 'toStream'. toStream is exactly the same as+-- reversing the stream after toStreamRev.+--+-- | Buffers the input stream to a pure stream in the reverse order of the+-- input.+--+-- /Warning!/ working on large streams accumulated as buffers in memory could+-- be very inefficient, consider using "Streamly.Array" instead.+--+-- /Internal/++-- xn : ... : x2 : x1 : []+{-# INLINABLE toStreamRev #-}+toStreamRev :: Monad m => Fold m a (SerialT Identity a)+toStreamRev = Fold (\xs x -> return $ x `K.cons` xs) (return K.nil) return++-- | Convert a stream to a pure stream.+--+-- @+-- toPure = foldr cons nil+-- @+--+-- /Internal/+--+{-# INLINE toPure #-}+toPure :: Monad m => SerialT m a -> m (SerialT Identity a)+toPure = foldr K.cons K.nil++-- | Convert a stream to a pure stream in reverse order.+--+-- @+-- toPureRev = foldl' (flip cons) nil+-- @+--+-- /Internal/+--+{-# INLINE toPureRev #-}+toPureRev :: Monad m => SerialT m a -> m (SerialT Identity a)+toPureRev = foldl' (flip K.cons) K.nil++------------------------------------------------------------------------------+-- Concurrent Application+------------------------------------------------------------------------------++infixr 0 |$+infixr 0 |$.++infixl 1 |&+infixl 1 |&.++-- | Parallel transform application operator; applies a stream transformation+-- function @t m a -> t m b@ to a stream @t m a@ concurrently; the input stream+-- is evaluated asynchronously in an independent thread yielding elements to a+-- buffer and the transformation function runs in another thread consuming the+-- input from the buffer. '|$' is just like regular function application+-- operator '$' except that it is concurrent.+--+-- If you read the signature as @(t m a -> t m b) -> (t m a -> t m b)@ you can+-- look at it as a transformation that converts a transform function to a+-- buffered concurrent transform function.+--+-- The following code prints a value every second even though each stage adds a+-- 1 second delay.+--+--+-- @+-- drain $+-- S.mapM (\\x -> threadDelay 1000000 >> print x)+-- |$ S.repeatM (threadDelay 1000000 >> return 1)+-- @+--+-- /Concurrent/+--+-- @since 0.3.0+{-# INLINE (|$) #-}+(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> (t m a -> t m b)+-- (|$) f = f . Async.mkAsync+(|$) f = f . D.mkParallel++-- | Same as '|$'.+--+-- /Internal/+--+{-# INLINE applyAsync #-}+applyAsync :: (IsStream t, MonadAsync m)+ => (t m a -> t m b) -> (t m a -> t m b)+applyAsync = (|$)++-- | Parallel reverse function application operator for streams; just like the+-- regular reverse function application operator '&' except that it is+-- concurrent.+--+-- @+-- drain $+-- S.repeatM (threadDelay 1000000 >> return 1)+-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)+-- @+--+-- /Concurrent/+--+-- @since 0.3.0+{-# INLINE (|&) #-}+(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b+x |& f = f |$ x++-- | Parallel fold application operator; applies a fold function @t m a -> m b@+-- to a stream @t m a@ concurrently; The the input stream is evaluated+-- asynchronously in an independent thread yielding elements to a buffer and+-- the folding action runs in another thread consuming the input from the+-- buffer.+--+-- If you read the signature as @(t m a -> m b) -> (t m a -> m b)@ you can look+-- at it as a transformation that converts a fold function to a buffered+-- concurrent fold function.+--+-- The @.@ at the end of the operator is a mnemonic for termination of the+-- stream.+--+-- @+-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()+-- |$. S.repeatM (threadDelay 1000000 >> return 1)+-- @+--+-- /Concurrent/+--+-- @since 0.3.0+{-# INLINE (|$.) #-}+(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> (t m a -> m b)+-- (|$.) f = f . Async.mkAsync+(|$.) f = f . D.mkParallel++-- | Same as '|$.'.+--+-- /Internal/+--+{-# INLINE foldAsync #-}+foldAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> (t m a -> m b)+foldAsync = (|$.)++-- | Parallel reverse function application operator for applying a run or fold+-- functions to a stream. Just like '|$.' except that the operands are reversed.+--+-- @+-- S.repeatM (threadDelay 1000000 >> return 1)+-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()+-- @+--+-- /Concurrent/+--+-- @since 0.3.0+{-# INLINE (|&.) #-}+(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b+x |&. f = f |$. x++------------------------------------------------------------------------------+-- General Transformation+------------------------------------------------------------------------------++-- | Use a 'Pipe' to transform a stream.+{-# INLINE transform #-}+transform :: (IsStream t, Monad m) => Pipe m a b -> t m a -> t m b+transform pipe xs = fromStreamD $ D.transform pipe (toStreamD xs)++------------------------------------------------------------------------------+-- Transformation by Folding (Scans)+------------------------------------------------------------------------------++-- XXX It may be useful to have a version of scan where we can keep the+-- accumulator independent of the value emitted. So that we do not necessarily+-- have to keep a value in the accumulator which we are not using. We can pass+-- an extraction function that will take the accumulator and the current value+-- of the element and emit the next value in the stream. That will also make it+-- possible to modify the accumulator after using it. In fact, the step function+-- can return new accumulator and the value to be emitted. The signature would+-- be more like mapAccumL. Or we can change the signature of scanx to+-- accommodate this.+--+-- | Strict left scan with an extraction function. Like 'scanl'', but applies a+-- user supplied extraction function (the third argument) at each step. This is+-- designed to work with the @foldl@ library. The suffix @x@ is a mnemonic for+-- extraction.+--+-- /Since: 0.7.0 (Monad m constraint)/+--+-- /Since 0.2.0/+{-# DEPRECATED scanx "Please use scanl followed by map instead." #-}+{-# INLINE scanx #-}+scanx :: (IsStream t, Monad m) => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b+scanx = P.scanlx'++-- XXX this needs to be concurrent+-- | Like 'scanl'' but with a monadic fold function.+--+-- @since 0.4.0+{-# INLINE scanlM' #-}+scanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> t m b+scanlM' step begin m = fromStreamD $ D.scanlM' step begin $ toStreamD m++-- | Strict left scan. Like 'map', 'scanl'' too is a one to one transformation,+-- however it adds an extra element.+--+-- @+-- > S.toList $ S.scanl' (+) 0 $ fromList [1,2,3,4]+-- [0,1,3,6,10]+-- @+--+-- @+-- > S.toList $ S.scanl' (flip (:)) [] $ S.fromList [1,2,3,4]+-- [[],[1],[2,1],[3,2,1],[4,3,2,1]]+-- @+--+-- The output of 'scanl'' is the initial value of the accumulator followed by+-- all the intermediate steps and the final result of 'foldl''.+--+-- By streaming the accumulated state after each fold step, we can share the+-- state across multiple stages of stream composition. Each stage can modify or+-- extend the state, do some processing with it and emit it for the next stage,+-- thus modularizing the stream processing. This can be useful in+-- stateful or event-driven programming.+--+-- Consider the following monolithic example, computing the sum and the product+-- of the elements in a stream in one go using a @foldl'@:+--+-- @+-- > S.foldl' (\\(s, p) x -> (s + x, p * x)) (0,1) $ S.fromList \[1,2,3,4]+-- (10,24)+-- @+--+-- Using @scanl'@ we can make it modular by computing the sum in the first+-- stage and passing it down to the next stage for computing the product:+--+-- @+-- > S.foldl' (\\(_, p) (s, x) -> (s, p * x)) (0,1)+-- $ S.scanl' (\\(s, _) x -> (s + x, x)) (0,1)+-- $ S.fromList \[1,2,3,4]+-- (10,24)+-- @+--+-- IMPORTANT: 'scanl'' evaluates the accumulator to WHNF. To avoid building+-- lazy expressions inside the accumulator, it is recommended that a strict+-- data structure is used for accumulator.+--+-- @since 0.2.0+{-# INLINE scanl' #-}+scanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b+scanl' step z m = fromStreamS $ S.scanl' step z $ toStreamS m++-- | Like 'scanl'' but does not stream the initial value of the accumulator.+--+-- > postscanl' f z xs = S.drop 1 $ S.scanl' f z xs+--+-- @since 0.7.0+{-# INLINE postscanl' #-}+postscanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b+postscanl' step z m = fromStreamD $ D.postscanl' step z $ toStreamD m++-- XXX this needs to be concurrent+-- | Like 'postscanl'' but with a monadic step function.+--+-- @since 0.7.0+{-# INLINE postscanlM' #-}+postscanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> t m b+postscanlM' step z m = fromStreamD $ D.postscanlM' step z $ toStreamD m++-- XXX prescanl does not sound very useful, enable only if there is a+-- compelling use case.+--+-- | Like scanl' but does not stream the final value of the accumulator.+--+-- @since 0.6.0+{-# INLINE prescanl' #-}+prescanl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> t m b+prescanl' step z m = fromStreamD $ D.prescanl' step z $ toStreamD m++-- XXX this needs to be concurrent+-- | Like postscanl' but with a monadic step function.+--+-- @since 0.6.0+{-# INLINE prescanlM' #-}+prescanlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> m b -> t m a -> t m b+prescanlM' step z m = fromStreamD $ D.prescanlM' step z $ toStreamD m++-- XXX this needs to be concurrent+-- | Like 'scanl1'' but with a monadic step function.+--+-- @since 0.6.0+{-# INLINE scanl1M' #-}+scanl1M' :: (IsStream t, Monad m) => (a -> a -> m a) -> t m a -> t m a+scanl1M' step m = fromStreamD $ D.scanl1M' step $ toStreamD m++-- | Like 'scanl'' but for a non-empty stream. The first element of the stream+-- is used as the initial value of the accumulator. Does nothing if the stream+-- is empty.+--+-- @+-- > S.toList $ S.scanl1 (+) $ fromList [1,2,3,4]+-- [1,3,6,10]+-- @+--+-- @since 0.6.0+{-# INLINE scanl1' #-}+scanl1' :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> t m a+scanl1' step m = fromStreamD $ D.scanl1' step $ toStreamD m++------------------------------------------------------------------------------+-- Scanning with a Fold+------------------------------------------------------------------------------++-- | Scan a stream using the given monadic fold.+--+-- @since 0.7.0+{-# INLINE scan #-}+scan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b+scan (Fold step begin done) = P.scanlMx' step begin done++-- | Postscan a stream using the given monadic fold.+--+-- @since 0.7.0+{-# INLINE postscan #-}+postscan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b+postscan (Fold step begin done) = P.postscanlMx' step begin done++------------------------------------------------------------------------------+-- Stateful Transformations+------------------------------------------------------------------------------++-- | Apply a function on every two successive elements of a stream. If the+-- stream consists of a single element the output is an empty stream.+--+-- /Internal/+--+{-# INLINE rollingMap #-}+rollingMap :: (IsStream t, Monad m) => (a -> a -> b) -> t m a -> t m b+rollingMap f m = fromStreamD $ D.rollingMap f $ toStreamD m++-- | Like 'rollingMap' but with an effectful map function.+--+-- /Internal/+--+{-# INLINE rollingMapM #-}+rollingMapM :: (IsStream t, Monad m) => (a -> a -> m b) -> t m a -> t m b+rollingMapM f m = fromStreamD $ D.rollingMapM f $ toStreamD m++------------------------------------------------------------------------------+-- Transformation by Filtering+------------------------------------------------------------------------------++-- | Include only those elements that pass a predicate.+--+-- @since 0.1.0+{-# INLINE filter #-}+#if __GLASGOW_HASKELL__ != 802+-- GHC 8.2.2 crashes with this code, when used with "stack"+filter :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a+filter p m = fromStreamS $ S.filter p $ toStreamS m+#else+filter :: IsStream t => (a -> Bool) -> t m a -> t m a+filter = K.filter+#endif++-- | Same as 'filter' but with a monadic predicate.+--+-- @since 0.4.0+{-# INLINE filterM #-}+filterM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a+filterM p m = fromStreamD $ D.filterM p $ toStreamD m++-- | Drop repeated elements that are adjacent to each other.+--+-- @since 0.6.0+{-# INLINE uniq #-}+uniq :: (Eq a, IsStream t, Monad m) => t m a -> t m a+uniq = fromStreamD . D.uniq . toStreamD++-- | Ensures that all the elements of the stream are identical and then returns+-- that unique element.+--+-- @since 0.6.0+{-# INLINE the #-}+the :: (Eq a, Monad m) => SerialT m a -> m (Maybe a)+the m = S.the (toStreamS m)++-- | Take first 'n' elements from the stream and discard the rest.+--+-- @since 0.1.0+{-# INLINE take #-}+take :: (IsStream t, Monad m) => Int -> t m a -> t m a+take n m = fromStreamS $ S.take n $ toStreamS+ (maxYields (Just (fromIntegral n)) m)++-- | End the stream as soon as the predicate fails on an element.+--+-- @since 0.1.0+{-# INLINE takeWhile #-}+takeWhile :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a+takeWhile p m = fromStreamS $ S.takeWhile p $ toStreamS m++-- | Same as 'takeWhile' but with a monadic predicate.+--+-- @since 0.4.0+{-# INLINE takeWhileM #-}+takeWhileM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a+takeWhileM p m = fromStreamD $ D.takeWhileM p $ toStreamD m++-- | @takeByTime duration@ yields stream elements upto specified time+-- @duration@. The duration starts when the stream is evaluated for the first+-- time, before the first element is yielded. The time duration is checked+-- before generating each element, if the duration has expired the stream+-- stops.+--+-- The total time taken in executing the stream is guaranteed to be /at least/+-- @duration@, however, because the duration is checked before generating an+-- element, the upper bound is indeterminate and depends on the time taken in+-- generating and processing the last element.+--+-- No element is yielded if the duration is zero. At least one element is+-- yielded if the duration is non-zero.+--+-- /Internal/+--+{-# INLINE takeByTime #-}+takeByTime ::(MonadIO m, IsStream t, TimeUnit64 d) => d -> t m a -> t m a+takeByTime d = fromStreamD . D.takeByTime d . toStreamD++-- | Discard first 'n' elements from the stream and take the rest.+--+-- @since 0.1.0+{-# INLINE drop #-}+drop :: (IsStream t, Monad m) => Int -> t m a -> t m a+drop n m = fromStreamS $ S.drop n $ toStreamS m++-- | Drop elements in the stream as long as the predicate succeeds and then+-- take the rest of the stream.+--+-- @since 0.1.0+{-# INLINE dropWhile #-}+dropWhile :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> t m a+dropWhile p m = fromStreamS $ S.dropWhile p $ toStreamS m++-- | Same as 'dropWhile' but with a monadic predicate.+--+-- @since 0.4.0+{-# INLINE dropWhileM #-}+dropWhileM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> t m a+dropWhileM p m = fromStreamD $ D.dropWhileM p $ toStreamD m++-- | @dropByTime duration@ drops stream elements until specified @duration@ has+-- passed. The duration begins when the stream is evaluated for the first+-- time. The time duration is checked /after/ generating a stream element, the+-- element is yielded if the duration has expired otherwise it is dropped.+--+-- The time elapsed before starting to generate the first element is /at most/+-- @duration@, however, because the duration expiry is checked after the+-- element is generated, the lower bound is indeterminate and depends on the+-- time taken in generating an element.+--+-- All elements are yielded if the duration is zero.+--+-- /Internal/+--+{-# INLINE dropByTime #-}+dropByTime ::(MonadIO m, IsStream t, TimeUnit64 d) => d -> t m a -> t m a+dropByTime d = fromStreamD . D.dropByTime d . toStreamD++------------------------------------------------------------------------------+-- Transformation by Mapping+------------------------------------------------------------------------------++-- |+-- @+-- mapM f = sequence . map f+-- @+--+-- Apply a monadic function to each element of the stream and replace it with+-- the output of the resulting action.+--+-- @+-- > drain $ S.mapM putStr $ S.fromList ["a", "b", "c"]+-- abc+--+-- drain $ S.replicateM 10 (return 1)+-- & (serially . S.mapM (\\x -> threadDelay 1000000 >> print x))+--+-- drain $ S.replicateM 10 (return 1)+-- & (asyncly . S.mapM (\\x -> threadDelay 1000000 >> print x))+-- @+--+-- /Concurrent (do not use with 'parallely' on infinite streams)/+--+-- @since 0.1.0+{-# INLINE_EARLY mapM #-}+mapM :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m b+mapM = K.mapM++{-# RULES "mapM serial" mapM = mapMSerial #-}+{-# INLINE mapMSerial #-}+mapMSerial :: Monad m => (a -> m b) -> SerialT m a -> SerialT m b+mapMSerial = Serial.mapM++-- |+-- @+-- sequence = mapM id+-- @+--+-- Replace the elements of a stream of monadic actions with the outputs of+-- those actions.+--+-- @+-- > drain $ S.sequence $ S.fromList [putStr "a", putStr "b", putStrLn "c"]+-- abc+--+-- drain $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)+-- & (serially . S.sequence)+--+-- drain $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)+-- & (asyncly . S.sequence)+-- @+--+-- /Concurrent (do not use with 'parallely' on infinite streams)/+--+-- @since 0.1.0+{-# INLINE sequence #-}+sequence :: (IsStream t, MonadAsync m) => t m (m a) -> t m a+sequence m = fromStreamS $ S.sequence (toStreamS m)++------------------------------------------------------------------------------+-- Transformation by Map and Filter+------------------------------------------------------------------------------++-- | Map a 'Maybe' returning function to a stream, filter out the 'Nothing'+-- elements, and return a stream of values extracted from 'Just'.+--+-- Equivalent to:+--+-- @+-- mapMaybe f = S.map 'fromJust' . S.filter 'isJust' . S.map f+-- @+--+-- @since 0.3.0+{-# INLINE mapMaybe #-}+mapMaybe :: (IsStream t, Monad m) => (a -> Maybe b) -> t m a -> t m b+mapMaybe f m = fromStreamS $ S.mapMaybe f $ toStreamS m++-- | Like 'mapMaybe' but maps a monadic function.+--+-- Equivalent to:+--+-- @+-- mapMaybeM f = S.map 'fromJust' . S.filter 'isJust' . S.mapM f+-- @+--+-- /Concurrent (do not use with 'parallely' on infinite streams)/+--+-- @since 0.3.0+{-# INLINE_EARLY mapMaybeM #-}+mapMaybeM :: (IsStream t, MonadAsync m, Functor (t m))+ => (a -> m (Maybe b)) -> t m a -> t m b+mapMaybeM f = fmap fromJust . filter isJust . K.mapM f++{-# RULES "mapMaybeM serial" mapMaybeM = mapMaybeMSerial #-}+{-# INLINE mapMaybeMSerial #-}+mapMaybeMSerial :: Monad m => (a -> m (Maybe b)) -> SerialT m a -> SerialT m b+mapMaybeMSerial f m = fromStreamD $ D.mapMaybeM f $ toStreamD m++------------------------------------------------------------------------------+-- Transformation by Reordering+------------------------------------------------------------------------------++-- XXX Use a compact region list to temporarily store the list, in both reverse+-- as well as in reverse'.+--+-- /Note:/ 'reverse'' is much faster than this, use that when performance+-- matters.+--+-- > reverse = S.foldlT (flip S.cons) S.nil+--+-- | Returns the elements of the stream in reverse order. The stream must be+-- finite. Note that this necessarily buffers the entire stream in memory.+--+-- /Since 0.7.0 (Monad m constraint)/+--+-- /Since: 0.1.1/+{-# INLINE reverse #-}+reverse :: (IsStream t, Monad m) => t m a -> t m a+reverse s = fromStreamS $ S.reverse $ toStreamS s++-- | Like 'reverse' but several times faster, requires a 'Storable' instance.+--+-- @since 0.7.0+{-# INLINE reverse' #-}+reverse' :: (IsStream t, MonadIO m, Storable a) => t m a -> t m a+reverse' s = fromStreamD $ D.reverse' $ toStreamD s++------------------------------------------------------------------------------+-- Transformation by Inserting+------------------------------------------------------------------------------++-- intersperseM = intersperseBySpan 1++-- | Generate a stream by inserting the result of a monadic action between+-- consecutive elements of the given stream. Note that the monadic action is+-- performed after the stream action before which its result is inserted.+--+-- @+-- > S.toList $ S.intersperseM (return ',') $ S.fromList "hello"+-- "h,e,l,l,o"+-- @+--+-- @since 0.5.0+{-# INLINE intersperseM #-}+intersperseM :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a+intersperseM m = fromStreamS . S.intersperseM m . toStreamS++-- | Generate a stream by inserting a given element between consecutive+-- elements of the given stream.+--+-- @+-- > S.toList $ S.intersperse ',' $ S.fromList "hello"+-- "h,e,l,l,o"+-- @+--+-- @since 0.7.0+{-# INLINE intersperse #-}+intersperse :: (IsStream t, MonadAsync m) => a -> t m a -> t m a+intersperse a = fromStreamS . S.intersperse a . toStreamS++-- | Insert a monadic action after each element in the stream.+--+-- @since 0.7.0+{-# INLINE intersperseSuffix #-}+intersperseSuffix :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a+intersperseSuffix m = fromStreamD . D.intersperseSuffix m . toStreamD++-- | Perform a side effect after each element of a stream. The output of the+-- effectful action is discarded, therefore, the input stream remains+-- unchanged.+--+-- @+-- > S.mapM_ putChar $ S.intersperseSuffix_ (threadDelay 1000000) $ S.fromList "hello"+-- @+--+-- /Internal/+--+{-# INLINE intersperseSuffix_ #-}+intersperseSuffix_ :: (IsStream t, Monad m) => m b -> t m a -> t m a+intersperseSuffix_ m = Serial.mapM (\x -> void m >> return x)++-- | Introduces a delay of specified seconds after each element of a stream.+--+-- /Internal/+--+{-# INLINE delayPost #-}+delayPost :: (IsStream t, MonadIO m) => Double -> t m a -> t m a+delayPost n = intersperseSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000++-- | Like 'intersperseSuffix' but intersperses a monadic action into the input+-- stream after every @n@ elements and after the last element.+--+-- @+-- > S.toList $ S.intersperseSuffixBySpan 2 (return ',') $ S.fromList "hello"+-- "he,ll,o,"+-- @+--+-- /Internal/+--+{-# INLINE intersperseSuffixBySpan #-}+intersperseSuffixBySpan :: (IsStream t, MonadAsync m)+ => Int -> m a -> t m a -> t m a+intersperseSuffixBySpan n eff =+ fromStreamD . D.intersperseSuffixBySpan n eff . toStreamD++{-+-- | Intersperse a monadic action into the input stream after every @n@+-- elements.+--+-- @+-- > S.toList $ S.intersperseBySpan 2 (return ',') $ S.fromList "hello"+-- "he,ll,o"+-- @+--+-- @since 0.7.0+{-# INLINE intersperseBySpan #-}+intersperseBySpan :: IsStream t => Int -> m a -> t m a -> t m a+intersperseBySpan _n _f _xs = undefined+-}++-- | Intersperse a monadic action into the input stream after every @n@+-- seconds.+--+-- @+-- > S.drain $ S.interjectSuffix 1 (putChar ',') $ S.mapM (\\x -> threadDelay 1000000 >> putChar x) $ S.fromList "hello"+-- "h,e,l,l,o"+-- @+--+-- @since 0.7.0+{-# INLINE interjectSuffix #-}+interjectSuffix+ :: (IsStream t, MonadAsync m)+ => Double -> m a -> t m a -> t m a+interjectSuffix n f xs = xs `Par.parallelFst` repeatM timed+ where timed = liftIO (threadDelay (round $ n * 1000000)) >> f++-- | @insertBy cmp elem stream@ inserts @elem@ before the first element in+-- @stream@ that is less than @elem@ when compared using @cmp@.+--+-- @+-- insertBy cmp x = 'mergeBy' cmp ('yield' x)+-- @+--+-- @+-- > S.toList $ S.insertBy compare 2 $ S.fromList [1,3,5]+-- [1,2,3,5]+-- @+--+-- @since 0.6.0+{-# INLINE insertBy #-}+insertBy ::+ (IsStream t, Monad m) => (a -> a -> Ordering) -> a -> t m a -> t m a+insertBy cmp x m = fromStreamS $ S.insertBy cmp x (toStreamS m)++------------------------------------------------------------------------------+-- Deleting+------------------------------------------------------------------------------++-- | Deletes the first occurrence of the element in the stream that satisfies+-- the given equality predicate.+--+-- @+-- > S.toList $ S.deleteBy (==) 3 $ S.fromList [1,3,3,5]+-- [1,3,5]+-- @+--+-- @since 0.6.0+{-# INLINE deleteBy #-}+deleteBy :: (IsStream t, Monad m) => (a -> a -> Bool) -> a -> t m a -> t m a+deleteBy cmp x m = fromStreamS $ S.deleteBy cmp x (toStreamS m)++------------------------------------------------------------------------------+-- Zipping+------------------------------------------------------------------------------++-- |+-- > indexed = S.postscanl' (\(i, _) x -> (i + 1, x)) (-1,undefined)+-- > indexed = S.zipWith (,) (S.enumerateFrom 0)+--+-- Pair each element in a stream with its index, starting from index 0.+--+-- @+-- > S.toList $ S.indexed $ S.fromList "hello"+-- [(0,'h'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]+-- @+--+-- @since 0.6.0+{-# INLINE indexed #-}+indexed :: (IsStream t, Monad m) => t m a -> t m (Int, a)+indexed = fromStreamD . D.indexed . toStreamD++-- |+-- > indexedR n = S.postscanl' (\(i, _) x -> (i - 1, x)) (n + 1,undefined)+-- > indexedR n = S.zipWith (,) (S.enumerateFromThen n (n - 1))+--+-- Pair each element in a stream with its index, starting from the+-- given index @n@ and counting down.+--+-- @+-- > S.toList $ S.indexedR 10 $ S.fromList "hello"+-- [(10,'h'),(9,'e'),(8,'l'),(7,'l'),(6,'o')]+-- @+--+-- @since 0.6.0+{-# INLINE indexedR #-}+indexedR :: (IsStream t, Monad m) => Int -> t m a -> t m (Int, a)+indexedR n = fromStreamD . D.indexedR n . toStreamD++------------------------------------------------------------------------------+-- Comparison+------------------------------------------------------------------------------++-- | Compare two streams for equality using an equality function.+--+-- @since 0.6.0+{-# INLINABLE eqBy #-}+eqBy :: (IsStream t, Monad m) => (a -> b -> Bool) -> t m a -> t m b -> m Bool+eqBy = P.eqBy++-- | Compare two streams lexicographically using a comparison function.+--+-- @since 0.6.0+{-# INLINABLE cmpBy #-}+cmpBy+ :: (IsStream t, Monad m)+ => (a -> b -> Ordering) -> t m a -> t m b -> m Ordering+cmpBy = P.cmpBy++------------------------------------------------------------------------------+-- Merge+------------------------------------------------------------------------------++-- | Merge two streams using a comparison function. The head elements of both+-- the streams are compared and the smaller of the two elements is emitted, if+-- both elements are equal then the element from the first stream is used+-- first.+--+-- If the streams are sorted in ascending order, the resulting stream would+-- also remain sorted in ascending order.+--+-- @+-- > S.toList $ S.mergeBy compare (S.fromList [1,3,5]) (S.fromList [2,4,6,8])+-- [1,2,3,4,5,6,8]+-- @+--+-- @since 0.6.0+{-# INLINABLE mergeBy #-}+mergeBy ::+ (IsStream t, Monad m) => (a -> a -> Ordering) -> t m a -> t m a -> t m a+mergeBy f m1 m2 = fromStreamS $ S.mergeBy f (toStreamS m1) (toStreamS m2)++-- | Like 'mergeBy' but with a monadic comparison function.+--+-- Merge two streams randomly:+--+-- @+-- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT+-- > S.toList $ S.mergeByM randomly (S.fromList [1,1,1,1]) (S.fromList [2,2,2,2])+-- [2,1,2,2,2,1,1,1]+-- @+--+-- Merge two streams in a proportion of 2:1:+--+-- @+-- proportionately m n = do+-- ref <- newIORef $ cycle $ concat [replicate m LT, replicate n GT]+-- return $ \\_ _ -> do+-- r <- readIORef ref+-- writeIORef ref $ tail r+-- return $ head r+--+-- main = do+-- f <- proportionately 2 1+-- xs <- S.toList $ S.mergeByM f (S.fromList [1,1,1,1,1,1]) (S.fromList [2,2,2])+-- print xs+-- @+-- @+-- [1,1,2,1,1,2,1,1,2]+-- @+--+-- @since 0.6.0+{-# INLINABLE mergeByM #-}+mergeByM+ :: (IsStream t, Monad m)+ => (a -> a -> m Ordering) -> t m a -> t m a -> t m a+mergeByM f m1 m2 = fromStreamS $ S.mergeByM f (toStreamS m1) (toStreamS m2)++{-+-- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.+{-# INLINABLE mergeEndByAny #-}+mergeEndByAny+ :: (IsStream t, Monad m)+ => (a -> a -> m Ordering) -> t m a -> t m a -> t m a+mergeEndByAny f m1 m2 = fromStreamD $+ D.mergeEndByAny f (toStreamD m1) (toStreamD m2)++-- Like 'mergeByM' but stops merging as soon as the first stream stops.+{-# INLINABLE mergeEndByFirst #-}+mergeEndByFirst+ :: (IsStream t, Monad m)+ => (a -> a -> m Ordering) -> t m a -> t m a -> t m a+mergeEndByFirst f m1 m2 = fromStreamS $+ D.mergeEndByFirst f (toStreamD m1) (toStreamD m2)+-}++-- Holding this back for now, we may want to use the name "merge" differently+{-+-- | Same as @'mergeBy' 'compare'@.+--+-- @+-- > S.toList $ S.merge (S.fromList [1,3,5]) (S.fromList [2,4,6,8])+-- [1,2,3,4,5,6,8]+-- @+--+-- @since 0.6.0+{-# INLINABLE merge #-}+merge ::+ (IsStream t, Monad m, Ord a) => t m a -> t m a -> t m a+merge = mergeBy compare+-}++-- | Like 'mergeBy' but merges concurrently (i.e. both the elements being+-- merged are generated concurrently).+--+-- @since 0.6.0+{-# INLINE mergeAsyncBy #-}+mergeAsyncBy :: (IsStream t, MonadAsync m)+ => (a -> a -> Ordering) -> t m a -> t m a -> t m a+mergeAsyncBy f = mergeAsyncByM (\a b -> return $ f a b)++-- | Like 'mergeByM' but merges concurrently (i.e. both the elements being+-- merged are generated concurrently).+--+-- @since 0.6.0+{-# INLINE mergeAsyncByM #-}+mergeAsyncByM :: (IsStream t, MonadAsync m)+ => (a -> a -> m Ordering) -> t m a -> t m a -> t m a+mergeAsyncByM f m1 m2 = fromStreamD $+ D.mergeByM f (D.mkParallelD $ toStreamD m1) (D.mkParallelD $ toStreamD m2)++------------------------------------------------------------------------------+-- Nesting+------------------------------------------------------------------------------++-- | @concatMapWith merge map stream@ is a two dimensional looping combinator.+-- The first argument specifies a merge or concat function that is used to+-- merge the streams generated by applying the second argument i.e. the @map@+-- function to each element of the input stream. The concat function could be+-- 'serial', 'parallel', 'async', 'ahead' or any other zip or merge function+-- and the second argument could be any stream generation function using a+-- seed.+--+-- /Compare 'foldMapWith'/+--+-- @since 0.7.0+{-# INLINE concatMapWith #-}+concatMapWith+ :: IsStream t+ => (forall c. t m c -> t m c -> t m c)+ -> (a -> t m b)+ -> t m a+ -> t m b+concatMapWith = K.concatMapBy++-- | Map a stream producing function on each element of the stream and then+-- flatten the results into a single stream.+--+-- @+-- concatMap = 'concatMapWith' 'Serial.serial'+-- concatMap f = 'concatMapM' (return . f)+-- @+--+-- @since 0.6.0+{-# INLINE concatMap #-}+concatMap ::(IsStream t, Monad m) => (a -> t m b) -> t m a -> t m b+concatMap f m = fromStreamD $ D.concatMap (toStreamD . f) (toStreamD m)++-- | Append the outputs of two streams, yielding all the elements from the+-- first stream and then yielding all the elements from the second stream.+--+-- IMPORTANT NOTE: This could be 100x faster than @serial/<>@ for appending a+-- few (say 100) streams because it can fuse via stream fusion. However, it+-- does not scale for a large number of streams (say 1000s) and becomes+-- qudartically slow. Therefore use this for custom appending of a few streams+-- but use 'concatMap' or 'concatMapWith serial' for appending @n@ streams or+-- infinite containers of streams.+--+-- @since 0.7.0+{-# INLINE append #-}+append ::(IsStream t, Monad m) => t m b -> t m b -> t m b+append m1 m2 = fromStreamD $ D.append (toStreamD m1) (toStreamD m2)++-- XXX Same as 'wSerial'. We should perhaps rename wSerial to interleave.+-- XXX Document the interleaving behavior of side effects in all the+-- interleaving combinators.+-- XXX Write time-domain equivalents of these. In the time domain we can+-- interleave two streams such that the value of second stream is always taken+-- from its last value even if no new value is being yielded, like+-- zipWithLatest. It would be something like interleaveWithLatest.+--+-- | Interleaves the outputs of two streams, yielding elements from each stream+-- alternately, starting from the first stream. If any of the streams finishes+-- early the other stream continues alone until it too finishes.+--+-- >>> :set -XOverloadedStrings+-- >>> interleave "ab" ",,,," :: SerialT Identity Char+-- fromList "a,b,,,"+-- >>> interleave "abcd" ",," :: SerialT Identity Char+-- fromList "a,b,cd"+--+-- 'interleave' is dual to 'interleaveMin', it can be called @interleaveMax@.+--+-- Do not use at scale in concatMapWith.+--+-- @since 0.7.0+{-# INLINE interleave #-}+interleave ::(IsStream t, Monad m) => t m b -> t m b -> t m b+interleave m1 m2 = fromStreamD $ D.interleave (toStreamD m1) (toStreamD m2)++-- | Interleaves the outputs of two streams, yielding elements from each stream+-- alternately, starting from the first stream. As soon as the first stream+-- finishes, the output stops, discarding the remaining part of the second+-- stream. In this case, the last element in the resulting stream would be from+-- the second stream. If the second stream finishes early then the first stream+-- still continues to yield elements until it finishes.+--+-- >>> :set -XOverloadedStrings+-- >>> interleaveSuffix "abc" ",,,," :: SerialT Identity Char+-- fromList "a,b,c,"+-- >>> interleaveSuffix "abc" "," :: SerialT Identity Char+-- fromList "a,bc"+--+-- 'interleaveSuffix' is a dual of 'interleaveInfix'.+--+-- Do not use at scale in concatMapWith.+--+-- @since 0.7.0+{-# INLINE interleaveSuffix #-}+interleaveSuffix ::(IsStream t, Monad m) => t m b -> t m b -> t m b+interleaveSuffix m1 m2 =+ fromStreamD $ D.interleaveSuffix (toStreamD m1) (toStreamD m2)++-- | Interleaves the outputs of two streams, yielding elements from each stream+-- alternately, starting from the first stream and ending at the first stream.+-- If the second stream is longer than the first, elements from the second+-- stream are infixed with elements from the first stream. If the first stream+-- is longer then it continues yielding elements even after the second stream+-- has finished.+--+-- >>> :set -XOverloadedStrings+-- >>> interleaveInfix "abc" ",,,," :: SerialT Identity Char+-- fromList "a,b,c"+-- >>> interleaveInfix "abc" "," :: SerialT Identity Char+-- fromList "a,bc"+--+-- 'interleaveInfix' is a dual of 'interleaveSuffix'.+--+-- Do not use at scale in concatMapWith.+--+-- @since 0.7.0+{-# INLINE interleaveInfix #-}+interleaveInfix ::(IsStream t, Monad m) => t m b -> t m b -> t m b+interleaveInfix m1 m2 =+ fromStreamD $ D.interleaveInfix (toStreamD m1) (toStreamD m2)++-- | Interleaves the outputs of two streams, yielding elements from each stream+-- alternately, starting from the first stream. The output stops as soon as any+-- of the two streams finishes, discarding the remaining part of the other+-- stream. The last element of the resulting stream would be from the longer+-- stream.+--+-- >>> :set -XOverloadedStrings+-- >>> interleaveMin "ab" ",,,," :: SerialT Identity Char+-- fromList "a,b,"+-- >>> interleaveMin "abcd" ",," :: SerialT Identity Char+-- fromList "a,b,c"+--+-- 'interleaveMin' is dual to 'interleave'.+--+-- Do not use at scale in concatMapWith.+--+-- @since 0.7.0+{-# INLINE interleaveMin #-}+interleaveMin ::(IsStream t, Monad m) => t m b -> t m b -> t m b+interleaveMin m1 m2 = fromStreamD $ D.interleaveMin (toStreamD m1) (toStreamD m2)++-- | Schedule the execution of two streams in a fair round-robin manner,+-- executing each stream once, alternately. Execution of a stream may not+-- necessarily result in an output, a stream may chose to @Skip@ producing an+-- element until later giving the other stream a chance to run. Therefore, this+-- combinator fairly interleaves the execution of two streams rather than+-- fairly interleaving the output of the two streams. This can be useful in+-- co-operative multitasking without using explicit threads. This can be used+-- as an alternative to `async`.+--+-- Do not use at scale in concatMapWith.+--+-- @since 0.7.0+{-# INLINE roundrobin #-}+roundrobin ::(IsStream t, Monad m) => t m b -> t m b -> t m b+roundrobin m1 m2 = fromStreamD $ D.roundRobin (toStreamD m1) (toStreamD m2)++-- | Map a stream producing monadic function on each element of the stream+-- and then flatten the results into a single stream. Since the stream+-- generation function is monadic, unlike 'concatMap', it can produce an+-- effect at the beginning of each iteration of the inner loop.+--+-- @since 0.6.0+{-# INLINE concatMapM #-}+concatMapM :: (IsStream t, Monad m) => (a -> m (t m b)) -> t m a -> t m b+concatMapM f m = fromStreamD $ D.concatMapM (fmap toStreamD . f) (toStreamD m)++-- | Like 'concatMap' but uses an 'Unfold' for stream generation. Unlike+-- 'concatMap' this can fuse the 'Unfold' code with the inner loop and+-- therefore provide many times better performance.+--+-- @since 0.7.0+{-# INLINE concatUnfold #-}+concatUnfold ::(IsStream t, Monad m) => Unfold m a b -> t m a -> t m b+concatUnfold u m = fromStreamD $ D.concatMapU u (toStreamD m)++-- | Like 'concatUnfold' but interleaves the streams in the same way as+-- 'interleave' behaves instead of appending them.+--+-- @since 0.7.0+{-# INLINE concatUnfoldInterleave #-}+concatUnfoldInterleave ::(IsStream t, Monad m)+ => Unfold m a b -> t m a -> t m b+concatUnfoldInterleave u m =+ fromStreamD $ D.concatUnfoldInterleave u (toStreamD m)++-- | Like 'concatUnfold' but executes the streams in the same way as+-- 'roundrobin'.+--+-- @since 0.7.0+{-# INLINE concatUnfoldRoundrobin #-}+concatUnfoldRoundrobin ::(IsStream t, Monad m)+ => Unfold m a b -> t m a -> t m b+concatUnfoldRoundrobin u m =+ fromStreamD $ D.concatUnfoldRoundrobin u (toStreamD m)++-- XXX we can swap the order of arguments to gintercalate so that the+-- definition of concatUnfold becomes simpler? The first stream should be+-- infixed inside the second one. However, if we change the order in+-- "interleave" as well similarly, then that will make it a bit unintuitive.+--+-- > concatUnfold unf str =+-- > gintercalate unf str (UF.nilM (\_ -> return ())) (repeat ())+--+-- | 'interleaveInfix' followed by unfold and concat.+--+-- /Internal/+{-# INLINE gintercalate #-}+gintercalate+ :: (IsStream t, Monad m)+ => Unfold m a c -> t m a -> Unfold m b c -> t m b -> t m c+gintercalate unf1 str1 unf2 str2 =+ D.fromStreamD $ D.gintercalate+ unf1 (D.toStreamD str1)+ unf2 (D.toStreamD str2)++-- XXX The order of arguments in "intercalate" is consistent with the list+-- intercalate but inconsistent with gintercalate and other stream interleaving+-- combinators. We can change the order of the arguments in other combinators+-- but then 'interleave' combinator may become a bit unintuitive because we+-- will be starting with the second stream.++-- > intercalate seed unf str = gintercalate unf str unf (repeatM seed)+-- > intercalate a unf str = concatUnfold unf $ intersperse a str+--+-- | 'intersperse' followed by unfold and concat.+--+-- > unwords = intercalate " " UF.fromList+--+-- >>> intercalate " " UF.fromList ["abc", "def", "ghi"]+-- > "abc def ghi"+--+{-# INLINE intercalate #-}+intercalate :: (IsStream t, Monad m)+ => b -> Unfold m b c -> t m b -> t m c+intercalate seed unf str = D.fromStreamD $+ D.concatMapU unf $ D.intersperse seed (toStreamD str)++-- > interpose x unf str = gintercalate unf str UF.identity (repeat x)+--+-- | Unfold the elements of a stream, intersperse the given element between the+-- unfolded streams and then concat them into a single stream.+--+-- > unwords = S.interpose ' '+--+-- /Internal/+{-# INLINE interpose #-}+interpose :: (IsStream t, Monad m)+ => c -> Unfold m b c -> t m b -> t m c+interpose x unf str =+ D.fromStreamD $ D.interpose (return x) unf (D.toStreamD str)++-- | 'interleaveSuffix' followed by unfold and concat.+--+-- /Internal/+{-# INLINE gintercalateSuffix #-}+gintercalateSuffix+ :: (IsStream t, Monad m)+ => Unfold m a c -> t m a -> Unfold m b c -> t m b -> t m c+gintercalateSuffix unf1 str1 unf2 str2 =+ D.fromStreamD $ D.gintercalateSuffix+ unf1 (D.toStreamD str1)+ unf2 (D.toStreamD str2)++-- > intercalateSuffix seed unf str = gintercalateSuffix unf str unf (repeatM seed)+-- > intercalateSuffix a unf str = concatUnfold unf $ intersperseSuffix a str+--+-- | 'intersperseSuffix' followed by unfold and concat.+--+-- > unlines = intercalateSuffix "\n" UF.fromList+--+-- >>> intercalate "\n" UF.fromList ["abc", "def", "ghi"]+-- > "abc\ndef\nghi\n"+--+{-# INLINE intercalateSuffix #-}+intercalateSuffix :: (IsStream t, Monad m)+ => b -> Unfold m b c -> t m b -> t m c+intercalateSuffix seed unf str = fromStreamD $ D.concatMapU unf+ $ D.intersperseSuffix (return seed) (D.toStreamD str)++-- interposeSuffix x unf str = gintercalateSuffix unf str UF.identity (repeat x)+--+-- | Unfold the elements of a stream, append the given element after each+-- unfolded stream and then concat them into a single stream.+--+-- > unlines = S.interposeSuffix '\n'+--+-- /Internal/+{-# INLINE interposeSuffix #-}+interposeSuffix :: (IsStream t, Monad m)+ => c -> Unfold m b c -> t m b -> t m c+interposeSuffix x unf str =+ D.fromStreamD $ D.interposeSuffix (return x) unf (D.toStreamD str)++------------------------------------------------------------------------------+-- Flattening Trees+------------------------------------------------------------------------------++-- | Like 'iterateM' but using a stream generator function.+--+-- /Internal/+--+{-# INLINE concatMapIterateWith #-}+concatMapIterateWith+ :: IsStream t+ => (forall c. t m c -> t m c -> t m c)+ -> (a -> t m a)+ -> t m a+ -> t m a+concatMapIterateWith combine f xs = concatMapWith combine go xs+ where+ go x = yield x `combine` concatMapWith combine go (f x)++-- concatMapIterateLeftsWith+--+-- | Traverse a forest with recursive tree structures whose non-leaf nodes are+-- of type @a@ and leaf nodes are of type @b@, flattening all the trees into+-- streams and combining the streams into a single stream consisting of both+-- leaf and non-leaf nodes.+--+-- 'concatMapTreeWith' is a generalization of 'concatMap', using a recursive+-- feedback loop to append the non-leaf nodes back to the input stream enabling+-- recursive traversal. 'concatMap' flattens a single level nesting whereas+-- 'concatMapTreeWith' flattens a recursively nested structure.+--+-- Traversing a directory tree recursively is a canonical use case of+-- 'concatMapTreeWith'.+--+-- @+-- concatMapTreeWith combine f xs = concatMapIterateWith combine g xs+-- where+-- g (Left tree) = f tree+-- g (Right leaf) = nil+-- @+--+-- /Internal/+--+{-# INLINE concatMapTreeWith #-}+concatMapTreeWith+ :: IsStream t+ => (forall c. t m c -> t m c -> t m c)+ -> (a -> t m (Either a b))+ -> t m (Either a b) -- Should be t m a?+ -> t m (Either a b)+concatMapTreeWith combine f xs = concatMapWith combine go xs+ where+ go (Left tree) = yield (Left tree) `combine` concatMapWith combine go (f tree)+ go (Right leaf) = yield $ Right leaf++{-+-- | Like concatMapTreeWith but produces only stream of leaf elements.+-- On an either stream, iterate lefts but yield only rights.+--+-- concatMapEitherYieldRightsWith combine f xs =+-- catRights $ concatMapTreeWith combine f xs+--+{-# INLINE concatMapEitherYieldRightsWith #-}+concatMapEitherYieldRightsWith :: (IsStream t, MonadAsync m)+ => _ -> (a -> t m (Either a b)) -> t m (Either a b) -> t m b+concatMapEitherYieldRightsWith combine f xs = undefined+-}++{-+{-# INLINE concatUnfoldTree #-}+concatUnfoldTree :: (IsStream t, MonadAsync m)+ => Unfold m a (Either a b) -> t m (Either a b) -> t m (Either a b)+concatUnfoldTree unf xs = undefined+-}++------------------------------------------------------------------------------+-- Feedback loop+------------------------------------------------------------------------------++-- We can perhaps even implement the SVar using this. The stream we are mapping+-- on is the work queue. When evaluated it results in either a leaf element to+-- yield or a tail stream to queue back to the work queue.+--+-- | Flatten a stream with a feedback loop back into the input.+--+-- For example, exceptions generated by the output stream can be fed back to+-- the input to take any corrective action. The corrective action may be to+-- retry the action or do nothing or log the errors. For the retry case we need+-- a feedback loop.+--+-- /Internal/+--+{-# INLINE concatMapLoopWith #-}+concatMapLoopWith+ :: (IsStream t, MonadAsync m)+ => (forall x. t m x -> t m x -> t m x)+ -> (a -> t m (Either b c))+ -> (b -> t m a) -- ^ feedback function to feed @b@ back into input+ -> t m a+ -> t m c+concatMapLoopWith combine f fb xs =+ concatMapWith combine go $ concatMapWith combine f xs+ where+ go (Left b) = concatMapLoopWith combine f fb $ fb b+ go (Right c) = yield c++-- | Concat a stream of trees, generating only leaves.+--+-- Compare with 'concatMapTreeWith'. While the latter returns all nodes in the+-- tree, this one returns only the leaves.+--+-- Traversing a directory tree recursively and yielding on the files is a+-- canonical use case of 'concatMapTreeYieldLeavesWith'.+--+-- @+-- concatMapTreeYieldLeavesWith combine f = concatMapLoopWith combine f yield+-- @+--+-- /Internal/+--+{-# INLINE concatMapTreeYieldLeavesWith #-}+concatMapTreeYieldLeavesWith+ :: (IsStream t, MonadAsync m)+ => (forall x. t m x -> t m x -> t m x)+ -> (a -> t m (Either a b))+ -> t m a+ -> t m b+concatMapTreeYieldLeavesWith combine f = concatMapLoopWith combine f yield++------------------------------------------------------------------------------+-- Grouping/Splitting+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Grouping without looking at elements+------------------------------------------------------------------------------+--+------------------------------------------------------------------------------+-- Binary APIs+------------------------------------------------------------------------------+--++-- | @splitAt n f1 f2@ composes folds @f1@ and @f2@ such that first @n@+-- elements of its input are consumed by fold @f1@ and the rest of the stream+-- is consumed by fold @f2@.+--+-- > let splitAt_ n xs = S.fold (FL.splitAt n FL.toList FL.toList) $ S.fromList xs+--+-- >>> splitAt_ 6 "Hello World!"+-- > ("Hello ","World!")+--+-- >>> splitAt_ (-1) [1,2,3]+-- > ([],[1,2,3])+--+-- >>> splitAt_ 0 [1,2,3]+-- > ([],[1,2,3])+--+-- >>> splitAt_ 1 [1,2,3]+-- > ([1],[2,3])+--+-- >>> splitAt_ 3 [1,2,3]+-- > ([1,2,3],[])+--+-- >>> splitAt_ 4 [1,2,3]+-- > ([1,2,3],[])+--+-- @since 0.7.0++-- This can be considered as a two-fold version of 'ltake' where we take both+-- the segments instead of discarding the leftover.+--+{-# INLINE splitAt #-}+splitAt+ :: Monad m+ => Int+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+splitAt n (Fold stepL initialL extractL) (Fold stepR initialR extractR) =+ Fold step initial extract+ where+ initial = Tuple3' <$> return n <*> initialL <*> initialR++ step (Tuple3' i xL xR) input =+ if i > 0+ then stepL xL input >>= (\a -> return (Tuple3' (i - 1) a xR))+ else stepR xR input >>= (\b -> return (Tuple3' i xL b))++ extract (Tuple3' _ a b) = (,) <$> extractL a <*> extractR b++------------------------------------------------------------------------------+-- N-ary APIs+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Generalized grouping+------------------------------------------------------------------------------++-- This combinator is the most general grouping combinator and can be used to+-- implement all other grouping combinators.+--+-- XXX check if this can implement the splitOn combinator i.e. we can slide in+-- new elements, slide out old elements and incrementally compute the hash.+-- Also, can we implement the windowed classification combinators using this?+--+-- In fact this is a parse. Instead of using a special return value in the fold+-- we are using a mapping function.+--+-- Note that 'scanl'' (usually followed by a map to extract the desired value+-- from the accumulator) can be used to realize many implementations e.g. a+-- sliding window implementation. A scan followed by a mapMaybe is also a good+-- pattern to express many problems where we want to emit a filtered output and+-- not emit an output on every input.+--+-- Passing on of the initial accumulator value to the next fold is equivalent+-- to returning the leftover concept.++{-+-- | @groupScan splitter fold stream@ folds the input stream using @fold@.+-- @splitter@ is applied on the accumulator of the fold every time an item is+-- consumed by the fold. The fold continues until @splitter@ returns a 'Just'+-- value. A 'Just' result from the @splitter@ specifies a result to be emitted+-- in the output stream and the initial value of the accumulator for the next+-- group's fold. This allows us to control whether to start fresh for the next+-- fold or to continue from the previous fold's output.+--+{-# INLINE groupScan #-}+groupScan+ :: (IsStream t, Monad m)+ => (x -> m (Maybe (b, x))) -> Fold m a x -> t m a -> t m b+groupScan split fold m = undefined+-}++-- | Group the input stream into groups of @n@ elements each and then fold each+-- group using the provided fold function.+--+-- >> S.toList $ S.chunksOf 2 FL.sum (S.enumerateFromTo 1 10)+-- > [3,7,11,15,19]+--+-- This can be considered as an n-fold version of 'ltake' where we apply+-- 'ltake' repeatedly on the leftover stream until the stream exhausts.+--+-- @since 0.7.0+{-# INLINE chunksOf #-}+chunksOf+ :: (IsStream t, Monad m)+ => Int -> Fold m a b -> t m a -> t m b+chunksOf n f m = D.fromStreamD $ D.groupsOf n f (D.toStreamD m)++{-# INLINE chunksOf2 #-}+chunksOf2+ :: (IsStream t, Monad m)+ => Int -> m c -> Fold2 m c a b -> t m a -> t m b+chunksOf2 n action f m = D.fromStreamD $ D.groupsOf2 n action f (D.toStreamD m)++-- | @arraysOf n stream@ groups the elements in the input stream into arrays of+-- @n@ elements each.+--+-- Same as the following but may be more efficient:+--+-- > arraysOf n = S.chunksOf n (A.writeN n)+--+-- @since 0.7.0+{-# INLINE arraysOf #-}+arraysOf :: (IsStream t, MonadIO m, Storable a)+ => Int -> t m a -> t m (Array a)+arraysOf n = chunksOf n (writeNUnsafe n)++-- XXX we can implement this by repeatedly applying the 'lrunFor' fold.+-- XXX add this example after fixing the serial stream rate control+-- >>> S.toList $ S.take 5 $ intervalsOf 1 FL.sum $ constRate 2 $ S.enumerateFrom 1+-- > [3,7,11,15,19]+--+-- | Group the input stream into windows of @n@ second each and then fold each+-- group using the provided fold function.+--+-- @since 0.7.0+{-# INLINE intervalsOf #-}+intervalsOf+ :: (IsStream t, MonadAsync m)+ => Double -> Fold m a b -> t m a -> t m b+intervalsOf n f xs =+ splitWithSuffix isNothing (FL.lcatMaybes f)+ (interjectSuffix n (return Nothing) (Serial.map Just xs))++------------------------------------------------------------------------------+-- Element Aware APIs+------------------------------------------------------------------------------+--+------------------------------------------------------------------------------+-- Binary APIs+------------------------------------------------------------------------------++-- | Break the input stream into two groups, the first group takes the input as+-- long as the predicate applied to the first element of the stream and next+-- input element holds 'True', the second group takes the rest of the input.+--+spanBy+ :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+spanBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =+ Fold step initial extract++ where+ initial = Tuple3' <$> initialL <*> initialR <*> return (Tuple' Nothing True)++ step (Tuple3' a b (Tuple' (Just frst) isFirstG)) input =+ if cmp frst input && isFirstG+ then stepL a input+ >>= (\a' -> return (Tuple3' a' b (Tuple' (Just frst) isFirstG)))+ else stepR b input+ >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))++ step (Tuple3' a b (Tuple' Nothing isFirstG)) input =+ if isFirstG+ then stepL a input+ >>= (\a' -> return (Tuple3' a' b (Tuple' (Just input) isFirstG)))+ else stepR b input+ >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))++ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b++-- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the+-- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the+-- input.+--+-- > let span_ p xs = S.fold (S.span p FL.toList FL.toList) $ S.fromList xs+--+-- >>> span_ (< 1) [1,2,3]+-- > ([],[1,2,3])+--+-- >>> span_ (< 2) [1,2,3]+-- > ([1],[2,3])+--+-- >>> span_ (< 4) [1,2,3]+-- > ([1,2,3],[])+--+-- @since 0.7.0++-- This can be considered as a two-fold version of 'ltakeWhile' where we take+-- both the segments instead of discarding the leftover.+{-# INLINE span #-}+span+ :: Monad m+ => (a -> Bool)+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+span p (Fold stepL initialL extractL) (Fold stepR initialR extractR) =+ Fold step initial extract++ where++ initial = Tuple3' <$> initialL <*> initialR <*> return True++ step (Tuple3' a b isFirstG) input =+ if isFirstG && p input+ then stepL a input >>= (\a' -> return (Tuple3' a' b True))+ else stepR b input >>= (\a' -> return (Tuple3' a a' False))++ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b++-- |+-- > break p = span (not . p)+--+-- Break as soon as the predicate becomes 'True'. @break p f1 f2@ composes+-- folds @f1@ and @f2@ such that @f1@ stops consuming input as soon as the+-- predicate @p@ becomes 'True'. The rest of the input is consumed @f2@.+--+-- This is the binary version of 'splitBy'.+--+-- > let break_ p xs = S.fold (S.break p FL.toList FL.toList) $ S.fromList xs+--+-- >>> break_ (< 1) [3,2,1]+-- > ([3,2,1],[])+--+-- >>> break_ (< 2) [3,2,1]+-- > ([3,2],[1])+--+-- >>> break_ (< 4) [3,2,1]+-- > ([],[3,2,1])+--+-- @since 0.7.0+{-# INLINE break #-}+break+ :: Monad m+ => (a -> Bool)+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+break p = span (not . p)++-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.+-- predicate is applied to the previous and the next input elements.+{-# INLINE spanByRolling #-}+spanByRolling+ :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+spanByRolling cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =+ Fold step initial extract++ where+ initial = Tuple3' <$> initialL <*> initialR <*> return Nothing++ step (Tuple3' a b (Just frst)) input =+ if cmp input frst+ then stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))+ else stepR b input >>= (\b' -> return (Tuple3' a b' (Just input)))++ step (Tuple3' a b Nothing) input =+ stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))++ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b++------------------------------------------------------------------------------+-- N-ary APIs+------------------------------------------------------------------------------+--+-- | @groupsBy cmp f $ S.fromList [a,b,c,...]@ assigns the element @a@ to the+-- first group, if @a \`cmp` b@ is 'True' then @b@ is also assigned to the same+-- group. If @a \`cmp` c@ is 'True' then @c@ is also assigned to the same+-- group and so on. When the comparison fails a new group is started. Each+-- group is folded using the fold @f@ and the result of the fold is emitted in+-- the output stream.+--+-- >>> S.toList $ S.groupsBy (>) FL.toList $ S.fromList [1,3,7,0,2,5]+-- > [[1,3,7],[0,2,5]]+--+-- @since 0.7.0+{-# INLINE groupsBy #-}+groupsBy+ :: (IsStream t, Monad m)+ => (a -> a -> Bool)+ -> Fold m a b+ -> t m a+ -> t m b+groupsBy cmp f m = D.fromStreamD $ D.groupsBy cmp f (D.toStreamD m)++-- | Unlike @groupsBy@ this function performs a rolling comparison of two+-- successive elements in the input stream. @groupsByRolling cmp f $ S.fromList+-- [a,b,c,...]@ assigns the element @a@ to the first group, if @a \`cmp` b@ is+-- 'True' then @b@ is also assigned to the same group. If @b \`cmp` c@ is+-- 'True' then @c@ is also assigned to the same group and so on. When the+-- comparison fails a new group is started. Each group is folded using the fold+-- @f@.+--+-- >>> S.toList $ S.groupsByRolling (\a b -> a + 1 == b) FL.toList $ S.fromList [1,2,3,7,8,9]+-- > [[1,2,3],[7,8,9]]+--+-- @since 0.7.0+{-# INLINE groupsByRolling #-}+groupsByRolling+ :: (IsStream t, Monad m)+ => (a -> a -> Bool)+ -> Fold m a b+ -> t m a+ -> t m b+groupsByRolling cmp f m = D.fromStreamD $ D.groupsRollingBy cmp f (D.toStreamD m)++-- |+-- > groups = groupsBy (==)+-- > groups = groupsByRolling (==)+--+-- Groups contiguous spans of equal elements together in individual groups.+--+-- >>> S.toList $ S.groups FL.toList $ S.fromList [1,1,2,2]+-- > [[1,1],[2,2]]+--+-- @since 0.7.0+groups :: (IsStream t, Monad m, Eq a) => Fold m a b -> t m a -> t m b+groups = groupsBy (==)++------------------------------------------------------------------------------+-- Binary splitting on a separator+------------------------------------------------------------------------------++{-+-- | Find the first occurrence of the specified sequence in the input stream+-- and break the input stream into two parts, the first part consisting of the+-- stream before the sequence and the second part consisting of the sequence+-- and the rest of the stream.+--+-- > let breakOn_ pat xs = S.fold (S.breakOn pat FL.toList FL.toList) $ S.fromList xs+--+-- >>> breakOn_ "dear" "Hello dear world!"+-- > ("Hello ","dear world!")+--+{-# INLINE breakOn #-}+breakOn :: Monad m => Array a -> Fold m a b -> Fold m a c -> Fold m a (b,c)+breakOn pat f m = undefined+-}++------------------------------------------------------------------------------+-- N-ary split on a predicate+------------------------------------------------------------------------------++-- TODO: Use a Splitter configuration similar to the "split" package to make it+-- possible to express all splitting combinations. In general, we can have+-- infix/suffix/prefix/condensing of separators, dropping both leading/trailing+-- separators. We can have a single split operation taking the splitter config+-- as argument.++-- | Split on an infixed separator element, dropping the separator. Splits the+-- stream on separator elements determined by the supplied predicate, separator+-- is considered as infixed between two segments, if one side of the separator+-- is missing then it is parsed as an empty stream. The supplied 'Fold' is+-- applied on the split segments. With '-' representing non-separator elements+-- and '.' as separator, 'splitOn' splits as follows:+--+-- @+-- "--.--" => "--" "--"+-- "--." => "--" ""+-- ".--" => "" "--"+-- @+--+-- @splitOn (== x)@ is an inverse of @intercalate (S.yield x)@+--+-- Let's use the following definition for illustration:+--+-- > splitOn' p xs = S.toList $ S.splitOn p (FL.toList) (S.fromList xs)+--+-- >>> splitOn' (== '.') ""+-- [""]+--+-- >>> splitOn' (== '.') "."+-- ["",""]+--+-- >>> splitOn' (== '.') ".a"+-- > ["","a"]+--+-- >>> splitOn' (== '.') "a."+-- > ["a",""]+--+-- >>> splitOn' (== '.') "a.b"+-- > ["a","b"]+--+-- >>> splitOn' (== '.') "a..b"+-- > ["a","","b"]+--+-- @since 0.7.0++-- This can be considered as an n-fold version of 'breakOn' where we apply+-- 'breakOn' successively on the input stream, dropping the first element+-- of the second segment after each break.+--+{-# INLINE splitOn #-}+splitOn+ :: (IsStream t, Monad m)+ => (a -> Bool) -> Fold m a b -> t m a -> t m b+splitOn predicate f m =+ D.fromStreamD $ D.splitBy predicate f (D.toStreamD m)++-- | Like 'splitOn' but the separator is considered as suffixed to the segments+-- in the stream. A missing suffix at the end is allowed. A separator at the+-- beginning is parsed as empty segment. With '-' representing elements and+-- '.' as separator, 'splitOnSuffix' splits as follows:+--+-- @+-- "--.--." => "--" "--"+-- "--.--" => "--" "--"+-- ".--." => "" "--"+-- @+--+-- > splitOnSuffix' p xs = S.toList $ S.splitSuffixBy p (FL.toList) (S.fromList xs)+--+-- >>> splitOnSuffix' (== '.') ""+-- []+--+-- >>> splitOnSuffix' (== '.') "."+-- [""]+--+-- >>> splitOnSuffix' (== '.') "a"+-- ["a"]+--+-- >>> splitOnSuffix' (== '.') ".a"+-- > ["","a"]+--+-- >>> splitOnSuffix' (== '.') "a."+-- > ["a"]+--+-- >>> splitOnSuffix' (== '.') "a.b"+-- > ["a","b"]+--+-- >>> splitOnSuffix' (== '.') "a.b."+-- > ["a","b"]+--+-- >>> splitOnSuffix' (== '.') "a..b.."+-- > ["a","","b",""]+--+-- > lines = splitOnSuffix (== '\n')+--+-- @since 0.7.0++-- This can be considered as an n-fold version of 'breakPost' where we apply+-- 'breakPost' successively on the input stream, dropping the first element+-- of the second segment after each break.+--+{-# INLINE splitOnSuffix #-}+splitOnSuffix+ :: (IsStream t, Monad m)+ => (a -> Bool) -> Fold m a b -> t m a -> t m b+splitOnSuffix predicate f m =+ D.fromStreamD $ D.splitSuffixBy predicate f (D.toStreamD m)++-- | Like 'splitOn' after stripping leading, trailing, and repeated separators.+-- Therefore, @".a..b."@ with '.' as the separator would be parsed as+-- @["a","b"]@. In other words, its like parsing words from whitespace+-- separated text.+--+-- > wordsBy' p xs = S.toList $ S.wordsBy p (FL.toList) (S.fromList xs)+--+-- >>> wordsBy' (== ',') ""+-- > []+--+-- >>> wordsBy' (== ',') ","+-- > []+--+-- >>> wordsBy' (== ',') ",a,,b,"+-- > ["a","b"]+--+-- > words = wordsBy isSpace+--+-- @since 0.7.0++-- It is equivalent to splitting in any of the infix/prefix/suffix styles+-- followed by removal of empty segments.+{-# INLINE wordsBy #-}+wordsBy+ :: (IsStream t, Monad m)+ => (a -> Bool) -> Fold m a b -> t m a -> t m b+wordsBy predicate f m =+ D.fromStreamD $ D.wordsBy predicate f (D.toStreamD m)++-- | Like 'splitOnSuffix' but keeps the suffix attached to the resulting+-- splits.+--+-- > splitWithSuffix' p xs = S.toList $ S.splitWithSuffix p (FL.toList) (S.fromList xs)+--+-- >>> splitWithSuffix' (== '.') ""+-- []+--+-- >>> splitWithSuffix' (== '.') "."+-- ["."]+--+-- >>> splitWithSuffix' (== '.') "a"+-- ["a"]+--+-- >>> splitWithSuffix' (== '.') ".a"+-- > [".","a"]+--+-- >>> splitWithSuffix' (== '.') "a."+-- > ["a."]+--+-- >>> splitWithSuffix' (== '.') "a.b"+-- > ["a.","b"]+--+-- >>> splitWithSuffix' (== '.') "a.b."+-- > ["a.","b."]+--+-- >>> splitWithSuffix' (== '.') "a..b.."+-- > ["a.",".","b.","."]+--+-- @since 0.7.0++-- This can be considered as an n-fold version of 'breakPost' where we apply+-- 'breakPost' successively on the input stream.+--+{-# INLINE splitWithSuffix #-}+splitWithSuffix+ :: (IsStream t, Monad m)+ => (a -> Bool) -> Fold m a b -> t m a -> t m b+splitWithSuffix predicate f m =+ D.fromStreamD $ D.splitSuffixBy' predicate f (D.toStreamD m)++------------------------------------------------------------------------------+-- Split on a delimiter sequence+------------------------------------------------------------------------------++-- Int list examples for splitOn:+--+-- >>> splitList [] [1,2,3,3,4]+-- > [[1],[2],[3],[3],[4]]+--+-- >>> splitList [5] [1,2,3,3,4]+-- > [[1,2,3,3,4]]+--+-- >>> splitList [1] [1,2,3,3,4]+-- > [[],[2,3,3,4]]+--+-- >>> splitList [4] [1,2,3,3,4]+-- > [[1,2,3,3],[]]+--+-- >>> splitList [2] [1,2,3,3,4]+-- > [[1],[3,3,4]]+--+-- >>> splitList [3] [1,2,3,3,4]+-- > [[1,2],[],[4]]+--+-- >>> splitList [3,3] [1,2,3,3,4]+-- > [[1,2],[4]]+--+-- >>> splitList [1,2,3,3,4] [1,2,3,3,4]+-- > [[],[]]++-- | Like 'splitOn' but the separator is a sequence of elements instead of a+-- single element.+--+-- For illustration, let's define a function that operates on pure lists:+--+-- @+-- splitOnSeq' pat xs = S.toList $ S.splitOnSeq (A.fromList pat) (FL.toList) (S.fromList xs)+-- @+--+-- >>> splitOnSeq' "" "hello"+-- > ["h","e","l","l","o"]+--+-- >>> splitOnSeq' "hello" ""+-- > [""]+--+-- >>> splitOnSeq' "hello" "hello"+-- > ["",""]+--+-- >>> splitOnSeq' "x" "hello"+-- > ["hello"]+--+-- >>> splitOnSeq' "h" "hello"+-- > ["","ello"]+--+-- >>> splitOnSeq' "o" "hello"+-- > ["hell",""]+--+-- >>> splitOnSeq' "e" "hello"+-- > ["h","llo"]+--+-- >>> splitOnSeq' "l" "hello"+-- > ["he","","o"]+--+-- >>> splitOnSeq' "ll" "hello"+-- > ["he","o"]+--+-- 'splitOnSeq' is an inverse of 'intercalate'. The following law always holds:+--+-- > intercalate . splitOn == id+--+-- The following law holds when the separator is non-empty and contains none of+-- the elements present in the input lists:+--+-- > splitOn . intercalate == id+--+-- @since 0.7.0++-- XXX We can use a polymorphic vector implemented by Array# to represent the+-- sequence, that way we can avoid the Storable constraint. If we still need+-- Storable Array for performance, we can use a separate splitOnArray API for+-- that. We can also have an API where the sequence itself is a lazy stream, so+-- that we can search files in files for example.+{-# INLINE splitOnSeq #-}+splitOnSeq+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)+ => Array a -> Fold m a b -> t m a -> t m b+splitOnSeq patt f m = D.fromStreamD $ D.splitOn patt f (D.toStreamD m)++{-+-- This can be implemented easily using Rabin Karp+-- | Split on any one of the given patterns.+{-# INLINE splitOnAny #-}+splitOnAny+ :: (IsStream t, Monad m, Storable a, Integral a)+ => [Array a] -> Fold m a b -> t m a -> t m b+splitOnAny subseq f m = undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)+-}++-- | Like 'splitSuffixBy' but the separator is a sequence of elements, instead+-- of a predicate for a single element.+--+-- > splitSuffixOn_ pat xs = S.toList $ S.splitSuffixOn (A.fromList pat) (FL.toList) (S.fromList xs)+--+-- >>> splitSuffixOn_ "." ""+-- [""]+--+-- >>> splitSuffixOn_ "." "."+-- [""]+--+-- >>> splitSuffixOn_ "." "a"+-- ["a"]+--+-- >>> splitSuffixOn_ "." ".a"+-- > ["","a"]+--+-- >>> splitSuffixOn_ "." "a."+-- > ["a"]+--+-- >>> splitSuffixOn_ "." "a.b"+-- > ["a","b"]+--+-- >>> splitSuffixOn_ "." "a.b."+-- > ["a","b"]+--+-- >>> splitSuffixOn_ "." "a..b.."+-- > ["a","","b",""]+--+-- > lines = splitSuffixOn "\n"+--+-- @since 0.7.0+{-# INLINE splitOnSuffixSeq #-}+splitOnSuffixSeq+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)+ => Array a -> Fold m a b -> t m a -> t m b+splitOnSuffixSeq patt f m =+ D.fromStreamD $ D.splitSuffixOn False patt f (D.toStreamD m)++{-+-- | Like 'splitOn' but drops any empty splits.+--+{-# INLINE wordsOn #-}+wordsOn+ :: (IsStream t, Monad m, Storable a, Eq a)+ => Array a -> Fold m a b -> t m a -> t m b+wordsOn subseq f m = undefined -- D.fromStreamD $ D.wordsOn f subseq (D.toStreamD m)+-}++-- XXX use a non-monadic intersperse to remove the MonadAsync constraint.+--+-- | Like 'splitOnSeq' but splits the separator as well, as an infix token.+--+-- > splitOn'_ pat xs = S.toList $ S.splitOn' (A.fromList pat) (FL.toList) (S.fromList xs)+--+-- >>> splitOn'_ "" "hello"+-- > ["h","","e","","l","","l","","o"]+--+-- >>> splitOn'_ "hello" ""+-- > [""]+--+-- >>> splitOn'_ "hello" "hello"+-- > ["","hello",""]+--+-- >>> splitOn'_ "x" "hello"+-- > ["hello"]+--+-- >>> splitOn'_ "h" "hello"+-- > ["","h","ello"]+--+-- >>> splitOn'_ "o" "hello"+-- > ["hell","o",""]+--+-- >>> splitOn'_ "e" "hello"+-- > ["h","e","llo"]+--+-- >>> splitOn'_ "l" "hello"+-- > ["he","l","","l","o"]+--+-- >>> splitOn'_ "ll" "hello"+-- > ["he","ll","o"]+--+-- @since 0.7.0+{-# INLINE splitBySeq #-}+splitBySeq+ :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)+ => Array a -> Fold m a b -> t m a -> t m b+splitBySeq patt f m =+ intersperseM (fold f (A.toStream patt)) $ splitOnSeq patt f m++-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.+--+-- > splitSuffixOn'_ pat xs = S.toList $ FL.splitSuffixOn' (A.fromList pat) (FL.toList) (S.fromList xs)+--+-- >>> splitSuffixOn'_ "." ""+-- [""]+--+-- >>> splitSuffixOn'_ "." "."+-- ["."]+--+-- >>> splitSuffixOn'_ "." "a"+-- ["a"]+--+-- >>> splitSuffixOn'_ "." ".a"+-- > [".","a"]+--+-- >>> splitSuffixOn'_ "." "a."+-- > ["a."]+--+-- >>> splitSuffixOn'_ "." "a.b"+-- > ["a.","b"]+--+-- >>> splitSuffixOn'_ "." "a.b."+-- > ["a.","b."]+--+-- >>> splitSuffixOn'_ "." "a..b.."+-- > ["a.",".","b.","."]+--+-- @since 0.7.0+{-# INLINE splitWithSuffixSeq #-}+splitWithSuffixSeq+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)+ => Array a -> Fold m a b -> t m a -> t m b+splitWithSuffixSeq patt f m =+ D.fromStreamD $ D.splitSuffixOn True patt f (D.toStreamD m)++{-+-- This can be implemented easily using Rabin Karp+-- | Split post any one of the given patterns.+{-# INLINE splitSuffixOnAny #-}+splitSuffixOnAny+ :: (IsStream t, Monad m, Storable a, Integral a)+ => [Array a] -> Fold m a b -> t m a -> t m b+splitSuffixOnAny subseq f m = undefined+ -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)+-}++------------------------------------------------------------------------------+-- Nested Split+------------------------------------------------------------------------------++-- | Consider a chunked stream of container elements e.g. a stream of @Word8@+-- chunked as a stream of arrays of @Word8@. @splitInnerBy splitter joiner+-- stream@ splits the inner containers @f a@ using the @splitter@ function and+-- joins back the resulting fragments from splitting across multiple containers+-- using the @joiner@ function such that the transformed output stream is+-- consolidated as one container per segment of the split.+--+-- CAUTION! This is not a true streaming function as the container size after+-- the split and merge may not be bounded.+--+-- @since 0.7.0+{-# INLINE splitInnerBy #-}+splitInnerBy+ :: (IsStream t, Monad m)+ => (f a -> m (f a, Maybe (f a))) -- splitter+ -> (f a -> f a -> m (f a)) -- joiner+ -> t m (f a)+ -> t m (f a)+splitInnerBy splitter joiner xs =+ D.fromStreamD $ D.splitInnerBy splitter joiner $ D.toStreamD xs++-- | Like 'splitInnerBy' but splits assuming the separator joins the segment in+-- a suffix style.+--+-- @since 0.7.0+{-# INLINE splitInnerBySuffix #-}+splitInnerBySuffix+ :: (IsStream t, Monad m, Eq (f a), Monoid (f a))+ => (f a -> m (f a, Maybe (f a))) -- splitter+ -> (f a -> f a -> m (f a)) -- joiner+ -> t m (f a)+ -> t m (f a)+splitInnerBySuffix splitter joiner xs =+ D.fromStreamD $ D.splitInnerBySuffix splitter joiner $ D.toStreamD xs++------------------------------------------------------------------------------+-- Reorder in sequence+------------------------------------------------------------------------------++{-+-- Buffer until the next element in sequence arrives. The function argument+-- determines the difference in sequence numbers. This could be useful in+-- implementing sequenced streams, for example, TCP reassembly.+{-# INLINE reassembleBy #-}+reassembleBy+ :: (IsStream t, Monad m)+ => Fold m a b+ -> (a -> a -> Int)+ -> t m a+ -> t m b+reassembleBy = undefined+-}++------------------------------------------------------------------------------+-- Distributing+------------------------------------------------------------------------------++-- | Tap the data flowing through a stream into a 'Fold'. For example, you may+-- add a tap to log the contents flowing through the stream. The fold is used+-- only for effects, its result is discarded.+--+-- @+-- Fold m a b+-- |+-- -----stream m a ---------------stream m a-----+--+-- @+--+-- @+-- > S.drain $ S.tap (FL.drainBy print) (S.enumerateFromTo 1 2)+-- 1+-- 2+-- @+--+-- Compare with 'trace'.+--+-- @since 0.7.0+{-# INLINE tap #-}+tap :: (IsStream t, Monad m) => FL.Fold m a b -> t m a -> t m a+tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)++-- | @tapOffsetEvery offset n@ taps every @n@th element in the stream+-- starting at @offset@. @offset@ can be between @0@ and @n - 1@. Offset 0+-- means start at the first element in the stream. If the offset is outside+-- this range then @offset `mod` n@ is used as offset.+--+-- @+-- >>> S.drain $ S.tapOffsetEvery 0 2 (FL.mapM print FL.toList) $ S.enumerateFromTo 0 10+-- > [0,2,4,6,8,10]+-- @+--+-- /Internal/+--+{-# INLINE tapOffsetEvery #-}+tapOffsetEvery :: (IsStream t, Monad m)+ => Int -> Int -> FL.Fold m a b -> t m a -> t m a+tapOffsetEvery offset n f xs =+ D.fromStreamD $ D.tapOffsetEvery offset n f (D.toStreamD xs)++-- | Redirect a copy of the stream to a supplied fold and run it concurrently+-- in an independent thread. The fold may buffer some elements. The buffer size+-- is determined by the prevailing 'maxBuffer' setting.+--+-- @+-- Stream m a -> m b+-- |+-- -----stream m a ---------------stream m a-----+--+-- @+--+-- @+-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)+-- 1+-- 2+-- @+--+-- Exceptions from the concurrently running fold are propagated to the current+-- computation. Note that, because of buffering in the fold, exceptions may be+-- delayed and may not correspond to the current element being processed in the+-- parent stream, but we guarantee that before the parent stream stops the tap+-- finishes and all exceptions from it are drained.+--+--+-- Compare with 'tap'.+--+-- /Internal/+{-# INLINE tapAsync #-}+tapAsync :: (IsStream t, MonadAsync m) => FL.Fold m a b -> t m a -> t m a+tapAsync f xs = D.fromStreamD $ D.tapAsync f (D.toStreamD xs)++-- | @pollCounts predicate transform fold stream@ counts those elements in the+-- stream that pass the @predicate@. The resulting count stream is sent to+-- another thread which transforms it using @transform@ and then folds it using+-- @fold@. The thread is automatically cleaned up if the stream stops or+-- aborts due to exception.+--+-- For example, to print the count of elements processed every second:+--+-- @+-- > S.drain $ S.pollCounts (const True) (S.rollingMap (-) . S.delayPost 1) (FL.drainBy print)+-- $ S.enumerateFrom 0+-- @+--+-- Note: This may not work correctly on 32-bit machines.+--+-- /Internal+--+{-# INLINE pollCounts #-}+pollCounts ::+ (IsStream t, MonadAsync m)+ => (a -> Bool)+ -> (t m Int -> t m Int)+ -> Fold m Int b+ -> t m a+ -> t m a+pollCounts predicate transf f xs =+ D.fromStreamD+ $ D.pollCounts predicate (D.toStreamD . transf . D.fromStreamD) f+ $ (D.toStreamD xs)++-- | Calls the supplied function with the number of elements consumed+-- every @n@ seconds. The given function is run in a separate thread+-- until the end of the stream. In case there is an exception in the+-- stream the thread is killed during the next major GC.+--+-- Note: The action is not guaranteed to run if the main thread exits.+--+-- @+-- > delay n = threadDelay (round $ n * 1000000) >> return n+-- > S.drain $ S.tapRate 2 (\\n -> print $ show n ++ " elements processed") (delay 1 S.|: delay 0.5 S.|: delay 0.5 S.|: S.nil)+-- 2 elements processed+-- 1 elements processed+-- @+--+-- Note: This may not work correctly on 32-bit machines.+--+-- /Internal+{-# INLINE tapRate #-}+tapRate ::+ (IsStream t, MonadAsync m, MonadCatch m)+ => Double+ -> (Int -> m b)+ -> t m a+ -> t m a+tapRate n f xs = D.fromStreamD $ D.tapRate n f $ (D.toStreamD xs)++-- | Apply a monadic function to each element flowing through the stream and+-- discard the results.+--+-- @+-- > S.drain $ S.trace print (S.enumerateFromTo 1 2)+-- 1+-- 2+-- @+--+-- Compare with 'tap'.+--+-- @since 0.7.0+{-# INLINE trace #-}+trace :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m a+trace f = mapM (\x -> void (f x) >> return x)++------------------------------------------------------------------------------+-- Windowed classification+------------------------------------------------------------------------------++-- We divide the stream into windows or chunks in space or time and each window+-- can be associated with a key, all events associated with a particular key in+-- the window can be folded to a single result. The stream can be split into+-- windows by size or by using a split predicate on the elements in the stream.+-- For example, when we receive a closing flag, we can close the window.+--+-- A "chunk" is a space window and a "session" is a time window. Are there any+-- other better short words to describe them. An alternative is to use+-- "swindow" and "twindow". Another word for "session" could be "spell".+--+-- TODO: To mark the position in space or time we can have Indexed or+-- TimeStamped types. That can make it easy to deal with the position indices+-- or timestamps.++------------------------------------------------------------------------------+-- Keyed Sliding Windows+------------------------------------------------------------------------------++{-+{-# INLINABLE classifySlidingChunks #-}+classifySlidingChunks+ :: (IsStream t, MonadAsync m, Ord k)+ => Int -- ^ window size+ -> Int -- ^ window slide+ -> Fold m a b -- ^ Fold to be applied to window events+ -> t m (k, a, Bool) -- ^ window key, data, close event+ -> t m (k, b)+classifySlidingChunks wsize wslide (Fold step initial extract) str+ = undefined++-- XXX Another variant could be to slide the window on an event, e.g. in TCP we+-- slide the send window when an ack is received and we slide the receive+-- window when a sequence is complete. Sliding is stateful in case of TCP,+-- sliding releases the send buffer or makes data available to the user from+-- the receive buffer.+{-# INLINABLE classifySlidingSessions #-}+classifySlidingSessions+ :: (IsStream t, MonadAsync m, Ord k)+ => Double -- ^ timer tick in seconds+ -> Double -- ^ time window size+ -> Double -- ^ window slide+ -> Fold m a b -- ^ Fold to be applied to window events+ -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp+ -> t m (k, b)+classifySlidingSessions tick interval slide (Fold step initial extract) str+ = undefined+-}++------------------------------------------------------------------------------+-- Sliding Window Buffers+------------------------------------------------------------------------------++-- These buffered versions could be faster than concurrent incremental folds of+-- all overlapping windows as in many cases we may not need all the values to+-- compute the fold, we can just compute the result using the old value and new+-- value. However, we may need the buffer once in a while, for example for+-- string search we usually compute the hash incrementally but when the hash+-- matches the hash of the pattern we need to compare the whole string.+--+-- XXX we should be able to implement sequence based splitting combinators+-- using this combinator.++{-+-- | Buffer n elements of the input in a ring buffer. When t new elements are+-- collected, slide the window to remove the same number of oldest elements,+-- insert the new elements, and apply an incremental fold on the sliding+-- window, supplying the outgoing elements, the new ring buffer as arguments.+slidingChunkBuffer+ :: (IsStream t, Monad m, Ord a, Storable a)+ => Int -- window size+ -> Int -- window slide+ -> Fold m (Ring a, Array a) b+ -> t m a+ -> t m b+slidingChunkBuffer = undefined++-- Buffer n seconds worth of stream elements of the input in a radix tree.+-- Every t seconds, remove the items that are older than n seconds, and apply+-- an incremental fold on the sliding window, supplying the outgoing elements,+-- and the new radix tree buffer as arguments.+slidingSessionBuffer+ :: (IsStream t, Monad m, Ord a, Storable a)+ => Int -- window size+ -> Int -- tick size+ -> Fold m (RTree a, Array a) b+ -> t m a+ -> t m b+slidingSessionBuffer = undefined+-}++------------------------------------------------------------------------------+-- Keyed Session Windows+------------------------------------------------------------------------------++{-+-- | Keyed variable size space windows. Close the window if we do not receive a+-- window event in the next "spaceout" elements.+{-# INLINABLE classifyChunksBy #-}+classifyChunksBy+ :: (IsStream t, MonadAsync m, Ord k)+ => Int -- ^ window spaceout (spread)+ -> Bool -- ^ reset the spaceout when a chunk window element is received+ -> Fold m a b -- ^ Fold to be applied to chunk window elements+ -> t m (k, a, Bool) -- ^ chunk key, data, last element+ -> t m (k, b)+classifyChunksBy spanout reset (Fold step initial extract) str = undefined++-- | Like 'classifyChunksOf' but the chunk size is reset if an element is+-- received within the chunk size window. The chunk gets closed only if no+-- element is received within the chunk window.+--+{-# INLINABLE classifyKeepAliveChunks #-}+classifyKeepAliveChunks+ :: (IsStream t, MonadAsync m, Ord k)+ => Int -- ^ window spaceout (spread)+ -> Fold m a b -- ^ Fold to be applied to chunk window elements+ -> t m (k, a, Bool) -- ^ chunk key, data, last element+ -> t m (k, b)+classifyKeepAliveChunks spanout = classifyChunksBy spanout True+-}++#if __GLASGOW_HASKELL__ < 800+#define Type *+#endif++data SessionState t m k a b = SessionState+ { sessionCurTime :: !AbsTime -- ^ time since last event+ , sessionEventTime :: !AbsTime -- ^ time as per last event+ , sessionCount :: !Int -- ^ total number sessions in progress+ , sessionTimerHeap :: H.Heap (H.Entry AbsTime k) -- ^ heap for timeouts+ , sessionKeyValueMap :: Map.Map k a -- ^ Stored sessions for keys+ , sessionOutputStream :: t (m :: Type -> Type) (k, b) -- ^ Completed sessions+ }++#undef Type++-- | @classifySessionsBy tick timeout idle pred f stream@ groups timestamped+-- events in an input event stream into sessions based on a session key. Each+-- element in the stream is an event consisting of a triple @(session key,+-- sesssion data, timestamp)@. @session key@ is a key that uniquely identifies+-- the session. All the events belonging to a session are folded using the+-- fold @f@ until the fold returns a 'Left' result or a timeout has occurred.+-- The session key and the result of the fold are emitted in the output stream+-- when the session is purged.+--+-- When @idle@ is 'False', @timeout@ is the maximum lifetime of a session in+-- seconds, measured from the @timestamp@ of the first event in that session.+-- When @idle@ is 'True' then the timeout is an idle timeout, it is reset after+-- every event received in the session.+--+-- @timestamp@ in an event characterizes the time when the input event was+-- generated, this is an absolute time measured from some @Epoch@. The notion+-- of current time is maintained by a monotonic event time clock using the+-- timestamps seen in the input stream. The latest timestamp seen till now is+-- used as the base for the current time. When no new events are seen, a timer+-- is started with a tick duration specified by @tick@. This timer is used to+-- detect session timeouts in the absence of new events.+--+-- The predicate @pred@ is invoked with the current session count, if it+-- returns 'True' a session is ejected from the session cache before inserting+-- a new session. This could be useful to alert or eject sessions when the+-- number of sessions becomes too high.+--+-- /Internal/+--++-- XXX Perhaps we should use an "Event a" type to represent timestamped data.+{-# INLINABLE classifySessionsBy #-}+classifySessionsBy+ :: (IsStream t, MonadAsync m, Ord k)+ => Double -- ^ timer tick in seconds+ -> Double -- ^ session timeout in seconds+ -> Bool -- ^ reset the timeout when an event is received+ -> (Int -> m Bool) -- ^ predicate to eject sessions based on session count+ -> Fold m a (Either b b) -- ^ Fold to be applied to session events+ -> t m (k, a, AbsTime) -- ^ session key, data, timestamp+ -> t m (k, b) -- ^ session key, fold result+classifySessionsBy tick timeout reset ejectPred+ (Fold step initial extract) str =+ concatMap (\session -> sessionOutputStream session)+ $ scanlM' sstep szero stream++ where++ timeoutMs = toRelTime (round (timeout * 1000) :: MilliSecond64)+ tickMs = toRelTime (round (tick * 1000) :: MilliSecond64)+ szero = SessionState+ { sessionCurTime = toAbsTime (0 :: MilliSecond64)+ , sessionEventTime = toAbsTime (0 :: MilliSecond64)+ , sessionCount = 0+ , sessionTimerHeap = H.empty+ , sessionKeyValueMap = Map.empty+ , sessionOutputStream = K.nil+ }++ -- We can eject sessions based on the current session count to limit+ -- memory consumption. There are two possible strategies:+ --+ -- 1) Eject old sessions or sessions beyond a certain/lower timeout+ -- threshold even before timeout, effectively reduce the timeout.+ -- 2) Drop creation of new sessions but keep accepting new events for the+ -- old ones.+ --+ -- We use the first strategy as of now.++ -- Got a new stream input element+ sstep (session@SessionState{..}) (Just (key, value, timestamp)) = do+ -- XXX we should use a heap in pinned memory to scale it to a large+ -- size+ --+ -- To detect session inactivity we keep a timestamp of the latest event+ -- in the Map along with the fold result. When we purge the session+ -- from the heap we match the timestamp in the heap with the timestamp+ -- in the Map, if the latest timestamp is newer and has not expired we+ -- reinsert the key in the heap.+ --+ -- XXX if the key is an Int, we can also use an IntMap for slightly+ -- better performance.+ --+ let curTime = max sessionEventTime timestamp+ accumulate v = do+ old <- case v of+ Nothing -> initial+ Just (Tuple' _ acc) -> return acc+ new <- step old value+ return $ Tuple' timestamp new+ mOld = Map.lookup key sessionKeyValueMap++ acc@(Tuple' _ fres) <- accumulate mOld+ res <- extract fres+ case res of+ Left x -> do+ -- deleting a key from the heap is expensive, so we never+ -- delete a key from heap, we just purge it from the Map and it+ -- gets purged from the heap on timeout. We just need an extra+ -- lookup in the Map when the key is purged from the heap, that+ -- should not be expensive.+ --+ let (mp, cnt) = case mOld of+ Nothing -> (sessionKeyValueMap, sessionCount)+ Just _ -> (Map.delete key sessionKeyValueMap+ , sessionCount - 1)+ return $ session+ { sessionCurTime = curTime+ , sessionEventTime = curTime+ , sessionCount = cnt+ , sessionKeyValueMap = mp+ , sessionOutputStream = yield (key, x)+ }+ Right _ -> do+ (hp1, mp1, out1, cnt1) <- do+ let vars = (sessionTimerHeap, sessionKeyValueMap,+ K.nil, sessionCount)+ case mOld of+ -- inserting new entry+ Nothing -> do+ -- Eject a session from heap and map is needed+ eject <- ejectPred sessionCount+ (hp, mp, out, cnt) <-+ if eject+ then ejectOne vars+ else return vars++ -- Insert the new session in heap+ let expiry = addToAbsTime timestamp timeoutMs+ hp' = H.insert (Entry expiry key) hp+ in return $ (hp', mp, out, (cnt + 1))+ -- updating old entry+ Just _ -> return vars++ let mp2 = Map.insert key acc mp1+ return $ SessionState+ { sessionCurTime = curTime+ , sessionEventTime = curTime+ , sessionCount = cnt1+ , sessionTimerHeap = hp1+ , sessionKeyValueMap = mp2+ , sessionOutputStream = out1+ }++ -- Got a timer tick event+ sstep (sessionState@SessionState{..}) Nothing =+ let curTime = addToAbsTime sessionCurTime tickMs+ in ejectExpired sessionState curTime++ fromEither e =+ case e of+ Left x -> x+ Right x -> x++ -- delete from map and output the fold accumulator+ ejectEntry hp mp out cnt acc key = do+ sess <- extract acc+ let out1 = (key, fromEither sess) `K.cons` out+ let mp1 = Map.delete key mp+ return (hp, mp1, out1, (cnt - 1))++ ejectOne (hp, mp, out, !cnt) = do+ let hres = H.uncons hp+ case hres of+ Just (Entry expiry key, hp1) -> do+ case Map.lookup key mp of+ Nothing -> ejectOne (hp1, mp, out, cnt)+ Just (Tuple' latestTS acc) -> do+ let expiry1 = addToAbsTime latestTS timeoutMs+ if not reset || expiry1 <= expiry+ then ejectEntry hp1 mp out cnt acc key+ else+ -- reset the session timeout and continue+ let hp2 = H.insert (Entry expiry1 key) hp1+ in ejectOne (hp2, mp, out, cnt)+ Nothing -> do+ assert (Map.null mp) (return ())+ return (hp, mp, out, cnt)++ ejectExpired (session@SessionState{..}) curTime = do+ (hp', mp', out, count) <-+ ejectLoop sessionTimerHeap sessionKeyValueMap K.nil sessionCount+ return $ session+ { sessionCurTime = curTime+ , sessionCount = count+ , sessionTimerHeap = hp'+ , sessionKeyValueMap = mp'+ , sessionOutputStream = out+ }++ where++ ejectLoop hp mp out !cnt = do+ let hres = H.uncons hp+ case hres of+ Just (Entry expiry key, hp1) -> do+ (eject, force) <- do+ if curTime >= expiry+ then return (True, False)+ else do+ r <- ejectPred cnt+ return (r, r)+ if eject+ then do+ case Map.lookup key mp of+ Nothing -> ejectLoop hp1 mp out cnt+ Just (Tuple' latestTS acc) -> do+ let expiry1 = addToAbsTime latestTS timeoutMs+ if expiry1 <= curTime || not reset || force+ then do+ (hp2,mp1,out1,cnt1) <-+ ejectEntry hp1 mp out cnt acc key+ ejectLoop hp2 mp1 out1 cnt1+ else+ -- reset the session timeout and continue+ let hp2 = H.insert (Entry expiry1 key) hp1+ in ejectLoop hp2 mp out cnt+ else return (hp, mp, out, cnt)+ Nothing -> do+ assert (Map.null mp) (return ())+ return (hp, mp, out, cnt)++ -- merge timer events in the stream+ stream = Serial.map Just str `Par.parallel` repeatM timer+ timer = do+ liftIO $ threadDelay (round $ tick * 1000000)+ return Nothing++-- | Like 'classifySessionsOf' but the session is kept alive if an event is+-- received within the session window. The session times out and gets closed+-- only if no event is received within the specified session window size.+--+-- If the ejection predicate returns 'True', the session that was idle for+-- the longest time is ejected before inserting a new session.+--+-- @+-- classifyKeepAliveSessions timeout pred = classifySessionsBy 1 timeout True pred+-- @+--+-- /Internal/+--+{-# INLINABLE classifyKeepAliveSessions #-}+classifyKeepAliveSessions+ :: (IsStream t, MonadAsync m, Ord k)+ => Double -- ^ session inactive timeout+ -> (Int -> m Bool) -- ^ predicate to eject sessions on session count+ -> Fold m a (Either b b) -- ^ Fold to be applied to session payload data+ -> t m (k, a, AbsTime) -- ^ session key, data, timestamp+ -> t m (k, b)+classifyKeepAliveSessions timeout ejectPred =+ classifySessionsBy 1 timeout True ejectPred++------------------------------------------------------------------------------+-- Keyed tumbling windows+------------------------------------------------------------------------------++-- Tumbling windows is a special case of sliding windows where the window slide+-- is the same as the window size. Or it can be a special case of session+-- windows where the reset flag is set to False.++-- XXX instead of using the early termination flag in the stream, we can use an+-- early terminating fold instead.++{-+-- | Split the stream into fixed size chunks of specified size. Within each+-- such chunk fold the elements in buckets identified by the keys. A particular+-- bucket fold can be terminated early if a closing flag is encountered in an+-- element for that key.+--+-- @since 0.7.0+{-# INLINABLE classifyChunksOf #-}+classifyChunksOf+ :: (IsStream t, MonadAsync m, Ord k)+ => Int -- ^ window size+ -> Fold m a b -- ^ Fold to be applied to window events+ -> t m (k, a, Bool) -- ^ window key, data, close event+ -> t m (k, b)+classifyChunksOf wsize = classifyChunksBy wsize False+-}++-- | Split the stream into fixed size time windows of specified interval in+-- seconds. Within each such window, fold the elements in sessions identified+-- by the session keys. The fold result is emitted in the output stream if the+-- fold returns a 'Left' result or if the time window ends.+--+-- Session @timestamp@ in the input stream is an absolute time from some epoch,+-- characterizing the time when the input element was generated. To detect+-- session window end, a monotonic event time clock is maintained synced with+-- the timestamps with a clock resolution of 1 second.+--+-- If the ejection predicate returns 'True', the session with the longest+-- lifetime is ejected before inserting a new session.+--+-- @+-- classifySessionsOf interval pred = classifySessionsBy 1 interval False pred+-- @+--+-- /Internal/+--+{-# INLINABLE classifySessionsOf #-}+classifySessionsOf+ :: (IsStream t, MonadAsync m, Ord k)+ => Double -- ^ time window size+ -> (Int -> m Bool) -- ^ predicate to eject sessions on session count+ -> Fold m a (Either b b) -- ^ Fold to be applied to session events+ -> t m (k, a, AbsTime) -- ^ session key, data, timestamp+ -> t m (k, b)+classifySessionsOf interval ejectPred =+ classifySessionsBy 1 interval False ejectPred++------------------------------------------------------------------------------+-- Exceptions+------------------------------------------------------------------------------++-- | Run a side effect before the stream yields its first element.+--+-- @since 0.7.0+{-# INLINE before #-}+before :: (IsStream t, Monad m) => m b -> t m a -> t m a+before action xs = D.fromStreamD $ D.before action $ D.toStreamD xs++-- | Run a side effect whenever the stream stops normally.+--+-- Prefer 'afterIO' over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+--+-- @since 0.7.0+{-# INLINE after #-}+after :: (IsStream t, Monad m) => m b -> t m a -> t m a+after action xs = D.fromStreamD $ D.after action $ D.toStreamD xs++-- | Run a side effect whenever the stream stops normally+-- or is garbage collected after a partial lazy evaluation.+--+-- /Internal/+--+{-# INLINE afterIO #-}+afterIO :: (IsStream t, MonadIO m, MonadBaseControl IO m) => m b -> t m a -> t m a+afterIO action xs = D.fromStreamD $ D.afterIO action $ D.toStreamD xs++-- | Run a side effect whenever the stream aborts due to an exception.+--+-- @since 0.7.0+{-# INLINE onException #-}+onException :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a+onException action xs = D.fromStreamD $ D.onException action $ D.toStreamD xs++-- | Run a side effect whenever the stream stops normally or aborts due to an+-- exception.+--+-- Prefer 'finallyIO' over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+--+-- @since 0.7.0+{-# INLINE finally #-}+finally :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a+finally action xs = D.fromStreamD $ D.finally action $ D.toStreamD xs++-- | Run a side effect whenever the stream stops normally, aborts due to an+-- exception or if it is garbage collected after a partial lazy evaluation.+--+-- /Internal/+--+{-# INLINE finallyIO #-}+finallyIO :: (IsStream t, MonadAsync m, MonadCatch m) => m b -> t m a -> t m a+finallyIO action xs = D.fromStreamD $ D.finallyIO action $ D.toStreamD xs++-- | Run the first action before the stream starts and remember its output,+-- generate a stream using the output, run the second action using the+-- remembered value as an argument whenever the stream ends normally or due to+-- an exception.+--+-- Prefer 'bracketIO' over this as the @after@ action in this combinator is not+-- executed if the unfold is partially evaluated lazily and then garbage+-- collected.+--+-- @since 0.7.0+{-# INLINE bracket #-}+bracket :: (IsStream t, MonadCatch m)+ => m b -> (b -> m c) -> (b -> t m a) -> t m a+bracket bef aft bet = D.fromStreamD $+ D.bracket bef aft (\x -> toStreamD $ bet x)++-- | Run the first action before the stream starts and remember its output,+-- generate a stream using the output, run the second action using the+-- remembered value as an argument whenever the stream ends normally, due to+-- an exception or if it is garbage collected after a partial lazy evaluation.+--+-- /Internal/+--+{-# INLINE bracketIO #-}+bracketIO :: (IsStream t, MonadAsync m, MonadCatch m)+ => m b -> (b -> m c) -> (b -> t m a) -> t m a+bracketIO bef aft bet = D.fromStreamD $+ D.bracketIO bef aft (\x -> toStreamD $ bet x) -- | When evaluating a stream if an exception occurs, stream evaluation aborts -- and the specified exception handler is run with the exception as argument.
src/Streamly/Memory/Array.hs view
@@ -1,8 +1,4 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE ScopedTypeVariables #-} #include "inline.hs"@@ -24,7 +20,7 @@ -- 'Storable' values of a given type, they cannot store non-serializable data -- like functions. Once created an array cannot be modified. Pinned memory -- allows efficient buffering of long lived data without adding any impact to--- GC. One array is just one pointer visible to GC and it does not have to+-- GC. One array is just one pointer visible to GC and it does not have to be -- copied across generations. Moreover, pinned memory allows communication -- with foreign consumers and producers (e.g. file or network IO) without -- copying the data.
src/Streamly/Memory/Malloc.hs view
@@ -1,11 +1,7 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE FlexibleContexts #-} #include "inline.hs"@@ -40,8 +36,8 @@ {-# INLINE mallocForeignPtrAlignedBytes #-} mallocForeignPtrAlignedBytes :: Int -> Int -> IO (GHC.ForeignPtr a) #ifdef USE_GHC_MALLOC-mallocForeignPtrAlignedBytes size alignment = do- GHC.mallocPlainForeignPtrAlignedBytes size alignment+mallocForeignPtrAlignedBytes =+ GHC.mallocPlainForeignPtrAlignedBytes #else mallocForeignPtrAlignedBytes size _alignment = do p <- mallocBytes size
src/Streamly/Memory/Ring.hs view
@@ -32,7 +32,7 @@ ) where import Control.Exception (assert)-import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)+import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, touchForeignPtr) import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr) import Foreign.Ptr (plusPtr, minusPtr, castPtr) import Foreign.Storable (Storable(..))@@ -40,6 +40,8 @@ import GHC.Ptr (Ptr(..)) import Prelude hiding (length, concat) +import Control.Monad.IO.Class (MonadIO(..))+ import qualified Streamly.Internal.Memory.Array.Types as A -- | A ring buffer is a mutable array of fixed size. Initially the array is@@ -67,7 +69,7 @@ let size = count * sizeOf (undefined :: a) fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a)) let p = unsafeForeignPtrToPtr fptr- return $ (Ring+ return (Ring { ringStart = fptr , ringBound = p `plusPtr` size }, p)@@ -103,7 +105,7 @@ unsafeEqArrayN Ring{..} rh A.Array{..} n = let !res = A.unsafeInlineIO $ do let rs = unsafeForeignPtrToPtr ringStart- let as = unsafeForeignPtrToPtr aStart+ as = unsafeForeignPtrToPtr aStart assert (aBound `minusPtr` as >= ringBound `minusPtr` rs) (return ()) let len = ringBound `minusPtr` rh r1 <- A.memcmp (castPtr rh) (castPtr as) (min len n)@@ -163,13 +165,21 @@ x <- peek p go (f acc x) (p `plusPtr` sizeOf (undefined :: a)) q +-- XXX Can we remove MonadIO here?+withForeignPtrM :: MonadIO m => ForeignPtr a -> (Ptr a -> m b) -> m b+withForeignPtrM fp fn = do+ r <- fn $ unsafeForeignPtrToPtr fp+ liftIO $ touchForeignPtr fp+ return r+ -- | Like unsafeFoldRing but with a monadic step function. {-# INLINE unsafeFoldRingM #-}-unsafeFoldRingM :: forall m a b. (Monad m, Storable a)+unsafeFoldRingM :: forall m a b. (MonadIO m, Storable a) => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b-unsafeFoldRingM ptr f z Ring{..} = go z (unsafeForeignPtrToPtr ringStart) ptr- where- go !acc !start !end+unsafeFoldRingM ptr f z Ring {..} =+ withForeignPtrM ringStart $ \x -> go z x ptr+ where+ go !acc !start !end | start == end = return acc | otherwise = do let !x = A.unsafeInlineIO $ peek start@@ -181,14 +191,15 @@ -- this would fold the ring starting from the oldest item to the newest item in -- the ring. {-# INLINE unsafeFoldRingFullM #-}-unsafeFoldRingFullM :: forall m a b. (Monad m, Storable a)+unsafeFoldRingFullM :: forall m a b. (MonadIO m, Storable a) => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b-unsafeFoldRingFullM rh f z rb@Ring{..} = go z rh- where- go !acc !start = do- let !x = A.unsafeInlineIO $ peek start- acc' <- f acc x- let ptr = advance rb start- if ptr == rh+unsafeFoldRingFullM rh f z rb@Ring {..} =+ withForeignPtrM ringStart $ \_ -> go z rh+ where+ go !acc !start = do+ let !x = A.unsafeInlineIO $ peek start+ acc' <- f acc x+ let ptr = advance rb start+ if ptr == rh then return acc' else go acc' ptr
src/Streamly/Network/Socket.hs view
@@ -9,7 +9,7 @@ -- -- A socket is a handle to a protocol endpoint. ----- This module provides APIs to read and write streams and arrays to and from+-- This module provides APIs to read and write streams and arrays from and to -- network sockets. Sockets may be connected or unconnected. Connected sockets -- can only send or recv data to/from the connected endpoint, therefore, APIs -- for connected sockets do not need to explicitly specify the remote endpoint.
src/Streamly/Prelude.hs view
@@ -6,7 +6,7 @@ {-# OPTIONS_GHC -Wno-orphans #-} #endif -#include "Streams/inline.hs"+#include "inline.hs" -- | -- Module : Streamly.Prelude@@ -781,7 +781,7 @@ -- left fold reconstructs in a LIFO style, thereby reversing the order of -- elements.. -- 3. A right fold has termination control and therefore can terminate early--- without going throught the entire input, a left fold cannot terminate+-- without going through the entire input, a left fold cannot terminate -- without consuming all of its input. For example, a right fold -- implementation of 'or' can terminate as soon as it finds the first 'True' -- element, whereas a left fold would necessarily go through the entire input
− src/Streamly/Streams/Ahead.hs
@@ -1,700 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving#-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX---- |--- Module : Streamly.Streams.Ahead--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Ahead- (- AheadT- , Ahead- , aheadly- , ahead- )-where--import Control.Concurrent.MVar (putMVar, takeMVar)-import Control.Exception (assert)-import Control.Monad (ap, void, when)-import Control.Monad.Base (MonadBase(..), liftBaseDefault)-import Control.Monad.Catch (MonadThrow, throwM)--- import Control.Monad.Error.Class (MonadError(..))-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader.Class (MonadReader(..))-import Control.Monad.State.Class (MonadState(..))-import Control.Monad.Trans.Class (MonadTrans(lift))-import Data.Heap (Heap, Entry(..))-import Data.IORef (IORef, readIORef, atomicModifyIORef, writeIORef)-import Data.Maybe (fromJust)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif-import GHC.Exts (inline)--import qualified Data.Heap as H--import Streamly.Streams.SVar (fromSVar)-import Streamly.Streams.Serial (map)-import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK- (IsStream(..), Stream, mkStream, foldStream, foldStreamShared,- foldStreamSVar)-import qualified Streamly.Streams.StreamK as K--import Prelude hiding (map)--#include "Instances.hs"------------------------------------------------------------------------------------ Ahead------------------------------------------------------------------------------------ Lookahead streams can execute multiple tasks concurrently, ahead of time,--- but always serve them in the same order as they appear in the stream. To--- implement lookahead streams efficiently we assign a sequence number to each--- task when the task is picked up for execution. When the task finishes, the--- output is tagged with the same sequence number and we rearrange the outputs--- in sequence based on that number.------ To explain the mechanism imagine that the current task at the head of the--- stream has a "token" to yield to the outputQueue. The ownership of the token--- is determined by the current sequence number is maintained in outputHeap.--- Sequence number is assigned when a task is queued. When a thread dequeues a--- task it picks up the sequence number as well and when the output is ready it--- uses the sequence number to queue the output to the outputQueue.------ The thread with current sequence number sends the output directly to the--- outputQueue. Other threads push the output to the outputHeap. When the task--- being queued on the heap is a stream of many elements we evaluate only the--- first element and keep the rest of the unevaluated computation in the heap.--- When such a task gets the "token" for outputQueue it evaluates and directly--- yields all the elements to the outputQueue without checking for the--- "token".------ Note that no two outputs in the heap can have the same sequence numbers and--- therefore we do not need a stable heap. We have also separated the buffer--- for the current task (outputQueue) and the pending tasks (outputHeap) so--- that the pending tasks cannot interfere with the current task. Note that for--- a single task just the outputQueue is enough and for the case of many--- threads just a heap is good enough. However we balance between these two--- cases, so that both are efficient.------ For bigger streams it may make sense to have separate buffers for each--- stream. However, for singleton streams this may become inefficient. However,--- if we do not have separate buffers, then the streams that come later in--- sequence may hog the buffer, hindering the streams that are ahead. For this--- reason we have a single element buffer limitation for the streams being--- executed in advance.------ This scheme works pretty efficiently with less than 40% extra overhead--- compared to the Async streams where we do not have any kind of sequencing of--- the outputs. It is especially devised so that we are most efficient when we--- have short tasks and need just a single thread. Also when a thread yields--- many items it can hold lockfree access to the outputQueue and do it--- efficiently.------ XXX Maybe we can start the ahead threads at a lower cpu and IO priority so--- that they do not hog the resources and hinder the progress of the threads in--- front of them.---- Left associated ahead expressions are expensive. We start a new SVar for--- each left associative expression. The queue is used only for right--- associated expression, we queue the right expression and execute the left.--- Thererefore the queue never has more than on item in it.------ XXX Also note that limiting concurrency for cases like "take 10" would not--- work well with left associative expressions, because we have no visibility--- about how much the left side of the expression would yield.------ XXX It may be a good idea to increment sequence numbers for each yield,--- currently a stream on the left side of the expression may yield many--- elements with the same sequene number. We can then use the seq number to--- enforce yieldMax and yieldLImit as well.---- Invariants:------ * A worker should always ensure that it pushes all the consecutive items in--- the heap to the outputQueue especially the items on behalf of the workers--- that have already left when we were holding the token. This avoids deadlock--- conditions when the later workers completion depends on the consumption of--- earlier results. For more details see comments in the consumer pull side--- code.--{-# INLINE underMaxHeap #-}-underMaxHeap ::- SVar Stream m a- -> Heap (Entry Int (AheadHeapEntry Stream m a))- -> IO Bool-underMaxHeap sv hp = do- (_, len) <- readIORef (outputQueue sv)-- -- XXX simplify this- let maxHeap = case maxBufferLimit sv of- Limited lim -> Limited $- max 0 (lim - fromIntegral len)- Unlimited -> Unlimited-- case maxHeap of- Limited lim -> do- active <- readIORef (workerCount sv)- return $ H.size hp + active <= fromIntegral lim- Unlimited -> return True---- Return value:--- True => stop--- False => continue-preStopCheck ::- SVar Stream m a- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Maybe Int)- -> IO Bool-preStopCheck sv heap =- -- check the stop condition under a lock before actually- -- stopping so that the whole herd does not stop at once.- withIORef heap $ \(hp, _) -> do- heapOk <- underMaxHeap sv hp- takeMVar (workerStopMVar sv)- let stop = do- putMVar (workerStopMVar sv) ()- return True- continue = do- putMVar (workerStopMVar sv) ()- return False- if heapOk- then- case yieldRateInfo sv of- Nothing -> continue- Just yinfo -> do- rateOk <- isBeyondMaxRate sv yinfo- if rateOk then continue else stop- else stop--abortExecution ::- IORef ([Stream m a], Int)- -> SVar Stream m a- -> Maybe WorkerInfo- -> Stream m a- -> IO ()-abortExecution q sv winfo m = do- reEnqueueAhead sv q m- incrementYieldLimit sv- sendStop sv winfo---- XXX In absence of a "noyield" primitive (i.e. do not pre-empt inside a--- critical section) from GHC RTS, we have a difficult problem. Assume we have--- a 100,000 threads producing output and queuing it to the heap for--- sequencing. The heap can be drained only by one thread at a time, any thread--- that finds that heap can be drained now, takes a lock and starts draining--- it, however the thread may get prempted in the middle of it holding the--- lock. Since that thread is holding the lock, the other threads cannot pick--- up the draining task, therefore they proceed to picking up the next task to--- execute. If the draining thread could yield voluntarily at a point where it--- has released the lock, then the next threads could pick up the draining--- instead of executing more tasks. When there are 100,000 threads the drainer--- gets a cpu share to run only 1:100000 of the time. This makes the heap--- accumulate a lot of output when we the buffer size is large.------ The solutions to this problem are:--- 1) make the other threads wait in a queue until the draining finishes--- 2) make the other threads queue and go away if draining is in progress------ In both cases we give the drainer a chance to run more often.----processHeap :: MonadIO m- => IORef ([Stream m a], Int)- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> AheadHeapEntry Stream m a- -> Int- -> Bool -- we are draining the heap before we stop- -> m ()-processHeap q heap st sv winfo entry sno stopping = loopHeap sno entry-- where-- stopIfNeeded ent seqNo r = do- stopIt <- liftIO $ preStopCheck sv heap- if stopIt- then liftIO $ do- -- put the entry back in the heap and stop- requeueOnHeapTop heap (Entry seqNo ent) seqNo- sendStop sv winfo- else runStreamWithYieldLimit True seqNo r-- loopHeap seqNo ent =- case ent of- AheadEntryNull -> nextHeap seqNo- AheadEntryPure a -> do- -- Use 'send' directly so that we do not account this in worker- -- latency as this will not be the real latency.- -- Don't stop the worker in this case as we are just- -- transferring available results from heap to outputQueue.- void $ liftIO $ send sv (ChildYield a)- nextHeap seqNo- AheadEntryStream r ->- if stopping- then stopIfNeeded ent seqNo r- else runStreamWithYieldLimit True seqNo r-- nextHeap prevSeqNo = do- res <- liftIO $ dequeueFromHeapSeq heap (prevSeqNo + 1)- case res of- Ready (Entry seqNo hent) -> loopHeap seqNo hent- Clearing -> liftIO $ sendStop sv winfo- Waiting _ ->- if stopping- then do- r <- liftIO $ preStopCheck sv heap- if r- then liftIO $ sendStop sv winfo- else processWorkQueue prevSeqNo- else inline processWorkQueue prevSeqNo-- processWorkQueue prevSeqNo = do- work <- dequeueAhead q- case work of- Nothing -> liftIO $ sendStop sv winfo- Just (m, seqNo) -> do- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if yieldLimitOk- then- if seqNo == prevSeqNo + 1- then processWithToken q heap st sv winfo m seqNo- else processWithoutToken q heap st sv winfo m seqNo- else liftIO $ abortExecution q sv winfo m-- -- We do not stop the worker on buffer full here as we want to proceed to- -- nextHeap anyway so that we can clear any subsequent entries. We stop- -- only in yield continuation where we may have a remaining stream to be- -- pushed on the heap.- singleStreamFromHeap seqNo a = do- void $ liftIO $ sendYield sv winfo (ChildYield a)- nextHeap seqNo-- -- XXX when we have an unfinished stream on the heap we cannot account all- -- the yields of that stream until it finishes, so if we have picked up- -- and executed more actions beyond that in the parent stream and put them- -- on the heap then they would eat up some yield limit which is not- -- correct, we will think that our yield limit is over even though we have- -- to yield items from unfinished stream before them. For this reason, if- -- there are pending items in the heap we drain them unconditionally- -- without considering the yield limit.- runStreamWithYieldLimit continue seqNo r = do- _ <- liftIO $ decrementYieldLimit sv- if continue -- see comment above -- && yieldLimitOk- then do- let stop = do- liftIO (incrementYieldLimit sv)- nextHeap seqNo- foldStreamSVar sv st- (yieldStreamFromHeap seqNo)- (singleStreamFromHeap seqNo)- stop- r- else liftIO $ do- let ent = Entry seqNo (AheadEntryStream r)- liftIO $ requeueOnHeapTop heap ent seqNo- incrementYieldLimit sv- sendStop sv winfo-- yieldStreamFromHeap seqNo a r = do- continue <- liftIO $ sendYield sv winfo (ChildYield a)- runStreamWithYieldLimit continue seqNo r--{-# NOINLINE drainHeap #-}-drainHeap :: MonadIO m- => IORef ([Stream m a], Int)- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-drainHeap q heap st sv winfo = do- r <- liftIO $ dequeueFromHeap heap- case r of- Ready (Entry seqNo hent) ->- processHeap q heap st sv winfo hent seqNo True- _ -> liftIO $ sendStop sv winfo--data HeapStatus = HContinue | HStop--processWithoutToken :: MonadIO m- => IORef ([Stream m a], Int)- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> Stream m a- -> Int- -> m ()-processWithoutToken q heap st sv winfo m seqNo = do- -- we have already decremented the yield limit for m- let stop = do- liftIO (incrementYieldLimit sv)- -- If the stream stops without yielding anything, and we do not put- -- anything on heap, but if heap was waiting for this seq number- -- then it will keep waiting forever, because we are never going to- -- put it on heap. So we have to put a null entry on heap even when- -- we stop.- toHeap AheadEntryNull-- foldStreamSVar sv st- (\a r -> toHeap $ AheadEntryStream $ K.cons a r)- (toHeap . AheadEntryPure)- stop- m-- where-- -- XXX to reduce contention each CPU can have its own heap- toHeap ent = do- -- Heap insertion is an expensive affair so we use a non CAS based- -- modification, otherwise contention and retries can make a thread- -- context switch and throw it behind other threads which come later in- -- sequence.- newHp <- liftIO $ atomicModifyIORef heap $ \(hp, snum) ->- let hp' = H.insert (Entry seqNo ent) hp- in assert (heapIsSane snum seqNo) ((hp', snum), hp')-- when (svarInspectMode sv) $- liftIO $ do- maxHp <- readIORef (maxHeapSize $ svarStats sv)- when (H.size newHp > maxHp) $- writeIORef (maxHeapSize $ svarStats sv) (H.size newHp)-- heapOk <- liftIO $ underMaxHeap sv newHp- let drainAndStop = drainHeap q heap st sv winfo- mainLoop = workLoopAhead q heap st sv winfo- status <-- case yieldRateInfo sv of- Nothing -> return HContinue- Just yinfo ->- case winfo of- Just info -> do- rateOk <- liftIO $ workerRateControl sv yinfo info- if rateOk- then return HContinue- else return HStop- Nothing -> return HContinue-- if heapOk- then- case status of- HContinue -> mainLoop- HStop -> drainAndStop- else drainAndStop--processWithToken :: MonadIO m- => IORef ([Stream m a], Int)- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> Stream m a- -> Int- -> m ()-processWithToken q heap st sv winfo action sno = do- -- Note, we enter this function with yield limit already decremented- -- XXX deduplicate stop in all invocations- let stop = do- liftIO (incrementYieldLimit sv)- loopWithToken (sno + 1)-- foldStreamSVar sv st (yieldOutput sno) (singleOutput sno) stop action-- where-- singleOutput seqNo a = do- continue <- liftIO $ sendYield sv winfo (ChildYield a)- if continue- then loopWithToken (seqNo + 1)- else do- liftIO $ updateHeapSeq heap (seqNo + 1)- drainHeap q heap st sv winfo-- -- XXX use a wrapper function around stop so that we never miss- -- incrementing the yield in a stop continuation. Essentiatlly all- -- "unstream" calls in this function must increment yield limit on stop.- yieldOutput seqNo a r = do- continue <- liftIO $ sendYield sv winfo (ChildYield a)- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if continue && yieldLimitOk- then do- let stop = do- liftIO (incrementYieldLimit sv)- loopWithToken (seqNo + 1)- foldStreamSVar sv st- (yieldOutput seqNo)- (singleOutput seqNo)- stop- r- else do- let ent = Entry seqNo (AheadEntryStream r)- liftIO $ requeueOnHeapTop heap ent seqNo- liftIO $ incrementYieldLimit sv- drainHeap q heap st sv winfo-- loopWithToken nextSeqNo = do- work <- dequeueAhead q- case work of- Nothing -> do- liftIO $ updateHeapSeq heap nextSeqNo- workLoopAhead q heap st sv winfo-- Just (m, seqNo) -> do- yieldLimitOk <- liftIO $ decrementYieldLimit sv- let undo = liftIO $ do- updateHeapSeq heap nextSeqNo- reEnqueueAhead sv q m- incrementYieldLimit sv- if yieldLimitOk- then- if seqNo == nextSeqNo- then do- let stop = do- liftIO (incrementYieldLimit sv)- loopWithToken (seqNo + 1)- foldStreamSVar sv st- (yieldOutput seqNo)- (singleOutput seqNo)- stop- m- else- -- To avoid a race when another thread puts something- -- on the heap and goes away, the consumer will not get- -- a doorBell and we will not clear the heap before- -- executing the next action. If the consumer depends- -- on the output that is stuck in the heap then this- -- will result in a deadlock. So we always clear the- -- heap before executing the next action.- undo >> workLoopAhead q heap st sv winfo- else undo >> drainHeap q heap st sv winfo---- XXX the yield limit changes increased the performance overhead by 30-40%.--- Just like AsyncT we can use an implementation without yeidlimit and even--- without pacing code to keep the performance higher in the unlimited and--- unpaced case.------ XXX The yieldLimit stuff is pretty invasive. We can instead do it by using--- three hooks, a pre-execute hook, a yield hook and a stop hook. In fact these--- hooks can be used for a more general implementation to even check predicates--- and not just yield limit.---- XXX we can remove the sv parameter as it can be derived from st--workLoopAhead :: MonadIO m- => IORef ([Stream m a], Int)- -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-workLoopAhead q heap st sv winfo = do- r <- liftIO $ dequeueFromHeap heap- case r of- Ready (Entry seqNo hent) ->- processHeap q heap st sv winfo hent seqNo False- Clearing -> liftIO $ sendStop sv winfo- Waiting _ -> do- -- Before we execute the next item from the work queue we check- -- if we are beyond the yield limit. It is better to check the- -- yield limit before we pick up the next item. Otherwise we- -- may have already started more tasks even though we may have- -- reached the yield limit. We can avoid this by taking active- -- workers into account, but that is not as reliable, because- -- workers may go away without picking up work and yielding a- -- value.- --- -- Rate control can be done either based on actual yields in- -- the output queue or based on any yield either to the heap or- -- to the output queue. In both cases we may have one issue or- -- the other. We chose to do this based on actual yields to the- -- output queue because it makes the code common to both async- -- and ahead streams.- --- work <- dequeueAhead q- case work of- Nothing -> liftIO $ sendStop sv winfo- Just (m, seqNo) -> do- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if yieldLimitOk- then- if seqNo == 0- then processWithToken q heap st sv winfo m seqNo- else processWithoutToken q heap st sv winfo m seqNo- -- If some worker decremented the yield limit but then- -- did not yield anything and therefore incremented it- -- later, then if we did not requeue m here we may find- -- the work queue empty and therefore miss executing- -- the remaining action.- else liftIO $ abortExecution q sv winfo m------------------------------------------------------------------------------------ WAhead------------------------------------------------------------------------------------ XXX To be implemented. Use a linked queue like WAsync and put back the--- remaining computation at the back of the queue instead of the heap, and--- increment the sequence number.---- The only difference between forkSVarAsync and this is that we run the left--- computation without a shared SVar.-forkSVarAhead :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-forkSVarAhead m1 m2 = mkStream $ \st stp sng yld -> do- sv <- newAheadVar st (concurrently (toStream m1) (toStream m2))- workLoopAhead- foldStream st stp sng yld (fromSVar sv)- where- concurrently ma mb = mkStream $ \st stp sng yld -> do- liftIO $ enqueue (fromJust $ streamVar st) mb- foldStream st stp sng yld ma---- | Polymorphic version of the 'Semigroup' operation '<>' of 'AheadT'.--- Merges two streams sequentially but with concurrent lookahead.------ @since 0.3.0-{-# INLINE ahead #-}-ahead :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-ahead m1 m2 = mkStream $ \st stp sng yld ->- case streamVar st of- Just sv | svarStyle sv == AheadVar -> do- liftIO $ enqueue sv (toStream m2)- -- Always run the left side on a new SVar to avoid complexity in- -- sequencing results. This means the left side cannot further- -- split into more ahead computations on the same SVar.- foldStream st stp sng yld m1- _ -> foldStreamShared st stp sng yld (forkSVarAhead m1 m2)---- | XXX we can implement it more efficienty by directly implementing instead--- of combining streams using ahead.-{-# INLINE consMAhead #-}-{-# SPECIALIZE consMAhead :: IO a -> AheadT IO a -> AheadT IO a #-}-consMAhead :: MonadAsync m => m a -> AheadT m a -> AheadT m a-consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)----------------------------------------------------------------------------------- AheadT----------------------------------------------------------------------------------- | The 'Semigroup' operation for 'AheadT' appends two streams. The combined--- stream behaves like a single stream with the actions from the second stream--- appended to the first stream. The combined stream is evaluated in the--- speculative style. This operation can be used to fold an infinite lazy--- container of streams.------ @--- import "Streamly"--- import qualified "Streamly.Prelude" as S--- import Control.Concurrent------ main = do--- xs \<- S.'toList' . 'aheadly' $ (p 1 |: p 2 |: nil) <> (p 3 |: p 4 |: nil)--- print xs--- where p n = threadDelay 1000000 >> return n--- @--- @--- [1,2,3,4]--- @------ Any exceptions generated by a constituent stream are propagated to the--- output stream.------ The monad instance of 'AheadT' may run each monadic continuation (bind)--- concurrently in a speculative manner, performing side effects in a partially--- ordered manner but producing the outputs in an ordered manner like--- 'SerialT'.------ @--- main = S.drain . 'aheadly' $ do--- n <- return 3 \<\> return 2 \<\> return 1--- S.yieldM $ do--- threadDelay (n * 1000000)--- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)--- @--- @--- ThreadId 40: Delay 1--- ThreadId 39: Delay 2--- ThreadId 38: Delay 3--- @------ @since 0.3.0-newtype AheadT m a = AheadT {getAheadT :: Stream m a}- deriving (MonadTrans)---- | A serial IO stream of elements of type @a@ with concurrent lookahead. See--- 'AheadT' documentation for more details.------ @since 0.3.0-type Ahead = AheadT IO---- | Fix the type of a polymorphic stream as 'AheadT'.------ @since 0.3.0-aheadly :: IsStream t => AheadT m a -> t m a-aheadly = K.adapt--instance IsStream AheadT where- toStream = getAheadT- fromStream = AheadT- consM = consMAhead- (|:) = consMAhead----------------------------------------------------------------------------------- Semigroup---------------------------------------------------------------------------------{-# INLINE mappendAhead #-}-{-# SPECIALIZE mappendAhead :: AheadT IO a -> AheadT IO a -> AheadT IO a #-}-mappendAhead :: MonadAsync m => AheadT m a -> AheadT m a -> AheadT m a-mappendAhead m1 m2 = fromStream $ ahead (toStream m1) (toStream m2)--instance MonadAsync m => Semigroup (AheadT m a) where- (<>) = mappendAhead----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance MonadAsync m => Monoid (AheadT m a) where- mempty = K.nil- mappend = (<>)----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------{-# INLINE concatMapAhead #-}-{-# SPECIALIZE concatMapAhead :: (a -> AheadT IO b) -> AheadT IO a -> AheadT IO b #-}-concatMapAhead :: MonadAsync m => (a -> AheadT m b) -> AheadT m a -> AheadT m b-concatMapAhead f m = fromStream $- K.concatMapBy ahead (\a -> K.adapt $ f a) (K.adapt m)--instance MonadAsync m => Monad (AheadT m) where- return = pure- {-# INLINE (>>=) #-}- (>>=) = flip concatMapAhead--instance (Monad m, MonadAsync m) => Applicative (AheadT m) where- pure = AheadT . K.yield- {-# INLINE (<*>) #-}- (<*>) = ap----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------MONAD_COMMON_INSTANCES(AheadT, MONADPARALLEL)
− src/Streamly/Streams/Async.hs
@@ -1,860 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving#-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX---- |--- Module : Streamly.Streams.Async--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Async- (- AsyncT- , Async- , asyncly- , async- , (<|) --deprecated- , mkAsync- , mkAsync'-- , WAsyncT- , WAsync- , wAsyncly- , wAsync- )-where--import Control.Concurrent (myThreadId)-import Control.Monad (ap)-import Control.Monad.Base (MonadBase(..), liftBaseDefault)-import Control.Monad.Catch (MonadThrow, throwM)-import Control.Concurrent.MVar (newEmptyMVar)--- import Control.Monad.Error.Class (MonadError(..))-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader.Class (MonadReader(..))-import Control.Monad.State.Class (MonadState(..))-import Control.Monad.Trans.Class (MonadTrans(lift))-import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)-import Data.IORef (IORef, newIORef, readIORef)-import Data.Maybe (fromJust)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif--import Prelude hiding (map)-import qualified Data.Set as S--import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)-import Streamly.Streams.SVar (fromSVar)-import Streamly.Streams.Serial (map)-import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK- (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared,- foldStreamSVar)-import qualified Streamly.Streams.StreamK as K--#include "Instances.hs"------------------------------------------------------------------------------------ Async----------------------------------------------------------------------------------{-# INLINE workLoopLIFO #-}-workLoopLIFO- :: MonadIO m- => IORef [Stream m a]- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-workLoopLIFO q st sv winfo = run-- where-- run = do- work <- dequeue- case work of- Nothing -> liftIO $ sendStop sv winfo- Just m -> foldStreamSVar sv st yieldk single run m-- single a = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res then run else liftIO $ sendStop sv winfo-- yieldk a r = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res- then foldStreamSVar sv st yieldk single run r- else liftIO $ do- enqueueLIFO sv q r- sendStop sv winfo-- dequeue = liftIO $ atomicModifyIORefCAS q $ \case- [] -> ([], Nothing)- x : xs -> (xs, Just x)---- We duplicate workLoop for yield limit and no limit cases because it has--- around 40% performance overhead in the worst case.------ XXX we can pass yinfo directly as an argument here so that we do not have to--- make a check every time.-{-# INLINE workLoopLIFOLimited #-}-workLoopLIFOLimited- :: MonadIO m- => IORef [Stream m a]- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-workLoopLIFOLimited q st sv winfo = run-- where-- run = do- work <- dequeue- case work of- Nothing -> liftIO $ sendStop sv winfo- Just m -> do- -- XXX This is just a best effort minimization of concurrency- -- to the yield limit. If the stream is made of concurrent- -- streams we do not reserve the yield limit in the constituent- -- streams before executing the action. This can be done- -- though, by sharing the yield limit ref with downstream- -- actions via state passing. Just a todo.- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if yieldLimitOk- then do- let stop = liftIO (incrementYieldLimit sv) >> run- foldStreamSVar sv st yieldk single stop m- -- Avoid any side effects, undo the yield limit decrement if we- -- never yielded anything.- else liftIO $ do- enqueueLIFO sv q m- incrementYieldLimit sv- sendStop sv winfo-- single a = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res then run else liftIO $ sendStop sv winfo-- -- XXX can we pass on the yield limit downstream to limit the concurrency- -- of constituent streams.- yieldk a r = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- yieldLimitOk <- liftIO $ decrementYieldLimit sv- let stop = liftIO (incrementYieldLimit sv) >> run- if res && yieldLimitOk- then foldStreamSVar sv st yieldk single stop r- else liftIO $ do- incrementYieldLimit sv- enqueueLIFO sv q r- sendStop sv winfo-- dequeue = liftIO $ atomicModifyIORefCAS q $ \case- [] -> ([], Nothing)- x : xs -> (xs, Just x)------------------------------------------------------------------------------------ WAsync------------------------------------------------------------------------------------ XXX we can remove sv as it is derivable from st--{-# INLINE workLoopFIFO #-}-workLoopFIFO- :: MonadIO m- => LinkedQueue (Stream m a)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-workLoopFIFO q st sv winfo = run-- where-- run = do- work <- liftIO $ tryPopR q- case work of- Nothing -> liftIO $ sendStop sv winfo- Just m -> foldStreamSVar sv st yieldk single run m-- single a = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res then run else liftIO $ sendStop sv winfo-- yieldk a r = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res- then foldStreamSVar sv st yieldk single run r- else liftIO $ do- enqueueFIFO sv q r- sendStop sv winfo--{-# INLINE workLoopFIFOLimited #-}-workLoopFIFOLimited- :: MonadIO m- => LinkedQueue (Stream m a)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m ()-workLoopFIFOLimited q st sv winfo = run-- where-- run = do- work <- liftIO $ tryPopR q- case work of- Nothing -> liftIO $ sendStop sv winfo- Just m -> do- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if yieldLimitOk- then do- let stop = liftIO (incrementYieldLimit sv) >> run- foldStreamSVar sv st yieldk single stop m- else liftIO $ do- enqueueFIFO sv q m- incrementYieldLimit sv- sendStop sv winfo-- single a = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- if res then run else liftIO $ sendStop sv winfo-- yieldk a r = do- res <- liftIO $ sendYield sv winfo (ChildYield a)- yieldLimitOk <- liftIO $ decrementYieldLimit sv- let stop = liftIO (incrementYieldLimit sv) >> run- if res && yieldLimitOk- then foldStreamSVar sv st yieldk single stop r- else liftIO $ do- incrementYieldLimit sv- enqueueFIFO sv q r- sendStop sv winfo------------------------------------------------------------------------------------ SVar creation--- This code belongs in SVar.hs but is kept here for perf reasons------------------------------------------------------------------------------------ XXX we have this function in this file because passing runStreamLIFO as a--- function argument to this function results in a perf degradation of more--- than 10%. Need to investigate what the root cause is.--- Interestingly, the same thing does not make any difference for Ahead.-getLifoSVar :: forall m a. MonadAsync m- => State Stream m a -> RunInIO m -> IO (SVar Stream m a)-getLifoSVar st mrun = do- outQ <- newIORef ([], 0)- outQMv <- newEmptyMVar- active <- newIORef 0- wfw <- newIORef False- running <- newIORef S.empty- q <- newIORef []- yl <- case getYieldLimit st of- Nothing -> return Nothing- Just x -> Just <$> newIORef x- rateInfo <- getYieldRateInfo st-- stats <- newSVarStats- tid <- myThreadId-- let isWorkFinished _ = null <$> readIORef q-- let isWorkFinishedLimited sv = do- yieldsDone <-- case remainingWork sv of- Just ref -> do- n <- readIORef ref- return (n <= 0)- Nothing -> return False- qEmpty <- null <$> readIORef q- return $ qEmpty || yieldsDone-- let getSVar :: SVar Stream m a- -> (SVar Stream m a -> m [ChildEvent a])- -> (SVar Stream m a -> m Bool)- -> (SVar Stream m a -> IO Bool)- -> (IORef [Stream m a]- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m())- -> SVar Stream m a- getSVar sv readOutput postProc workDone wloop = SVar- { outputQueue = outQ- , remainingWork = yl- , maxBufferLimit = getMaxBuffer st- , pushBufferSpace = undefined- , pushBufferPolicy = undefined- , pushBufferMVar = undefined- , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)- , yieldRateInfo = rateInfo- , outputDoorBell = outQMv- , readOutputQ = readOutput sv- , postProcess = postProc sv- , workerThreads = running- , workLoop = wloop q st{streamVar = Just sv} sv- , enqueue = enqueueLIFO sv q- , isWorkDone = workDone sv- , isQueueDone = workDone sv- , needDoorBell = wfw- , svarStyle = AsyncVar- , svarStopStyle = StopNone- , svarStopBy = undefined- , svarMrun = mrun- , workerCount = active- , accountThread = delThread sv- , workerStopMVar = undefined- , svarRef = Nothing- , svarInspectMode = getInspectMode st- , svarCreator = tid- , aheadWorkQueue = undefined- , outputHeap = undefined- , svarStats = stats- }-- let sv =- case getStreamRate st of- Nothing ->- case getYieldLimit st of- Nothing -> getSVar sv readOutputQBounded- postProcessBounded- isWorkFinished- workLoopLIFO- Just _ -> getSVar sv readOutputQBounded- postProcessBounded- isWorkFinishedLimited- workLoopLIFOLimited- Just _ ->- case getYieldLimit st of- Nothing -> getSVar sv readOutputQPaced- postProcessPaced- isWorkFinished- workLoopLIFO- Just _ -> getSVar sv readOutputQPaced- postProcessPaced- isWorkFinishedLimited- workLoopLIFOLimited- in return sv--getFifoSVar :: forall m a. MonadAsync m- => State Stream m a -> RunInIO m -> IO (SVar Stream m a)-getFifoSVar st mrun = do- outQ <- newIORef ([], 0)- outQMv <- newEmptyMVar- active <- newIORef 0- wfw <- newIORef False- running <- newIORef S.empty- q <- newQ- yl <- case getYieldLimit st of- Nothing -> return Nothing- Just x -> Just <$> newIORef x- rateInfo <- getYieldRateInfo st-- stats <- newSVarStats- tid <- myThreadId-- let isWorkFinished _ = nullQ q- let isWorkFinishedLimited sv = do- yieldsDone <-- case remainingWork sv of- Just ref -> do- n <- readIORef ref- return (n <= 0)- Nothing -> return False- qEmpty <- nullQ q- return $ qEmpty || yieldsDone-- let getSVar :: SVar Stream m a- -> (SVar Stream m a -> m [ChildEvent a])- -> (SVar Stream m a -> m Bool)- -> (SVar Stream m a -> IO Bool)- -> (LinkedQueue (Stream m a)- -> State Stream m a- -> SVar Stream m a- -> Maybe WorkerInfo- -> m())- -> SVar Stream m a- getSVar sv readOutput postProc workDone wloop = SVar- { outputQueue = outQ- , remainingWork = yl- , maxBufferLimit = getMaxBuffer st- , pushBufferSpace = undefined- , pushBufferPolicy = undefined- , pushBufferMVar = undefined- , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)- , yieldRateInfo = rateInfo- , outputDoorBell = outQMv- , readOutputQ = readOutput sv- , postProcess = postProc sv- , workerThreads = running- , workLoop = wloop q st{streamVar = Just sv} sv- , enqueue = enqueueFIFO sv q- , isWorkDone = workDone sv- , isQueueDone = workDone sv- , needDoorBell = wfw- , svarStyle = WAsyncVar- , svarStopStyle = StopNone- , svarStopBy = undefined- , svarMrun = mrun- , workerCount = active- , accountThread = delThread sv- , workerStopMVar = undefined- , svarRef = Nothing- , svarInspectMode = getInspectMode st- , svarCreator = tid- , aheadWorkQueue = undefined- , outputHeap = undefined- , svarStats = stats- }-- let sv =- case getStreamRate st of- Nothing ->- case getYieldLimit st of- Nothing -> getSVar sv readOutputQBounded- postProcessBounded- isWorkFinished- workLoopFIFO- Just _ -> getSVar sv readOutputQBounded- postProcessBounded- isWorkFinishedLimited- workLoopFIFOLimited- Just _ ->- case getYieldLimit st of- Nothing -> getSVar sv readOutputQPaced- postProcessPaced- isWorkFinished- workLoopFIFO- Just _ -> getSVar sv readOutputQPaced- postProcessPaced- isWorkFinishedLimited- workLoopFIFOLimited- in return sv--{-# INLINABLE newAsyncVar #-}-newAsyncVar :: MonadAsync m- => State Stream m a -> Stream m a -> m (SVar Stream m a)-newAsyncVar st m = do- mrun <- captureMonadState- sv <- liftIO $ getLifoSVar st mrun- sendFirstWorker sv m---- XXX Get rid of this?--- | Make a stream asynchronous, triggers the computation and returns a stream--- in the underlying monad representing the output generated by the original--- computation. The returned action is exhaustible and must be drained once. If--- not drained fully we may have a thread blocked forever and once exhausted it--- will always return 'empty'.------ @since 0.2.0-{-# INLINABLE mkAsync #-}-mkAsync :: (IsStream t, MonadAsync m) => t m a -> m (t m a)-mkAsync = mkAsync' defState--{-# INLINABLE mkAsync' #-}-mkAsync' :: (IsStream t, MonadAsync m) => State Stream m a -> t m a -> m (t m a)-mkAsync' st m = fmap fromSVar (newAsyncVar st (toStream m))---- | Create a new SVar and enqueue one stream computation on it.-{-# INLINABLE newWAsyncVar #-}-newWAsyncVar :: MonadAsync m- => State Stream m a -> Stream m a -> m (SVar Stream m a)-newWAsyncVar st m = do- mrun <- captureMonadState- sv <- liftIO $ getFifoSVar st mrun- sendFirstWorker sv m----------------------------------------------------------------------------------- Running streams concurrently----------------------------------------------------------------------------------- Concurrency rate control.------ Our objective is to create more threads on demand if the consumer is running--- faster than us. As soon as we encounter a concurrent composition we create a--- push pull pair of threads. We use an SVar for communication between the--- consumer, pulling from the SVar and the producer who is pushing to the SVar.--- The producer creates more threads if the SVar drains and becomes empty, that--- is the consumer is running faster.------ XXX Note 1: This mechanism can be problematic if the initial production--- latency is high, we may end up creating too many threads. So we need some--- way to monitor and use the latency as well. Having a limit on the dispatches--- (programmer controlled) may also help.------ TBD Note 2: We may want to run computations at the lower level of the--- composition tree serially even when they are composed using a parallel--- combinator. We can use 'serial' in place of 'async' and 'wSerial' in--- place of 'wAsync'. If we find that an SVar immediately above a computation--- gets drained empty we can switch to parallelizing the computation. For that--- we can use a state flag to fork the rest of the computation at any point of--- time inside the Monad bind operation if the consumer is running at a faster--- speed.------ TBD Note 3: the binary operation ('parallel') composition allows us to--- dispatch a chunkSize of only 1. If we have to dispatch in arbitrary--- chunksizes we will need to compose the parallel actions using a data--- constructor (A Free container) instead so that we can divide it in chunks of--- arbitrary size before dispatching. If the stream is composed of--- hierarchically composed grains of different sizes then we can always switch--- to a desired granularity depending on the consumer speed.------ TBD Note 4: for pure work (when we are not in the IO monad) we can divide it--- into just the number of CPUs.---- | Join two computations on the currently running 'SVar' queue for concurrent--- execution. When we are using parallel composition, an SVar is passed around--- as a state variable. We try to schedule a new parallel computation on the--- SVar passed to us. The first time, when no SVar exists, a new SVar is--- created. Subsequently, 'joinStreamVarAsync' may get called when a computation--- already scheduled on the SVar is further evaluated. For example, when (a--- `parallel` b) is evaluated it calls a 'joinStreamVarAsync' to put 'a' and 'b' on--- the current scheduler queue.------ The 'SVarStyle' required by the current composition context is passed as one--- of the parameters. If the scheduling and composition style of the new--- computation being scheduled is different than the style of the current SVar,--- then we create a new SVar and schedule it on that. The newly created SVar--- joins as one of the computations on the current SVar queue.------ Cases when we need to switch to a new SVar:------ * (x `parallel` y) `parallel` (t `parallel` u) -- all of them get scheduled on the same SVar--- * (x `parallel` y) `parallel` (t `async` u) -- @t@ and @u@ get scheduled on a new child SVar--- because of the scheduling policy change.--- * if we 'adapt' a stream of type 'async' to a stream of type--- 'Parallel', we create a new SVar at the transitioning bind.--- * When the stream is switching from disjunctive composition to conjunctive--- composition and vice-versa we create a new SVar to isolate the scheduling--- of the two.--forkSVarAsync :: (IsStream t, MonadAsync m)- => SVarStyle -> t m a -> t m a -> t m a-forkSVarAsync style m1 m2 = mkStream $ \st stp sng yld -> do- sv <- case style of- AsyncVar -> newAsyncVar st (concurrently (toStream m1) (toStream m2))- WAsyncVar -> newWAsyncVar st (concurrently (toStream m1) (toStream m2))- _ -> error "illegal svar type"- foldStream st stp sng yld $ fromSVar sv- where- concurrently ma mb = mkStream $ \st stp sng yld -> do- liftIO $ enqueue (fromJust $ streamVar st) mb- foldStreamShared st stp sng yld ma--{-# INLINE joinStreamVarAsync #-}-joinStreamVarAsync :: (IsStream t, MonadAsync m)- => SVarStyle -> t m a -> t m a -> t m a-joinStreamVarAsync style m1 m2 = mkStream $ \st stp sng yld ->- case streamVar st of- Just sv | svarStyle sv == style -> do- liftIO $ enqueue sv (toStream m2)- foldStreamShared st stp sng yld m1- _ -> foldStreamShared st stp sng yld (forkSVarAsync style m1 m2)----------------------------------------------------------------------------------- Semigroup and Monoid style compositions for parallel actions----------------------------------------------------------------------------------- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.--- Merges two streams possibly concurrently, preferring the--- elements from the left one when available.------ @since 0.2.0-{-# INLINE async #-}-async :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-async = joinStreamVarAsync AsyncVar---- | Same as 'async'.------ @since 0.1.0-{-# DEPRECATED (<|) "Please use 'async' instead." #-}-{-# INLINE (<|) #-}-(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-(<|) = async---- IMPORTANT: using a monomorphically typed and SPECIALIZED consMAsync makes a--- huge difference in the performance of consM in IsStream instance even we--- have a SPECIALIZE in the instance.------ | XXX we can implement it more efficienty by directly implementing instead--- of combining streams using async.-{-# INLINE consMAsync #-}-{-# SPECIALIZE consMAsync :: IO a -> AsyncT IO a -> AsyncT IO a #-}-consMAsync :: MonadAsync m => m a -> AsyncT m a -> AsyncT m a-consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)----------------------------------------------------------------------------------- AsyncT----------------------------------------------------------------------------------- | The 'Semigroup' operation for 'AsyncT' appends two streams. The combined--- stream behaves like a single stream with the actions from the second stream--- appended to the first stream. The combined stream is evaluated in the--- asynchronous style. This operation can be used to fold an infinite lazy--- container of streams.------ @--- import "Streamly"--- import qualified "Streamly.Prelude" as S--- import Control.Concurrent------ main = (S.toList . 'asyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print--- @--- @--- [1,2,3,4]--- @------ Any exceptions generated by a constituent stream are propagated to the--- output stream. The output and exceptions from a single stream are guaranteed--- to arrive in the same order in the resulting stream as they were generated--- in the input stream. However, the relative ordering of elements from--- different streams in the resulting stream can vary depending on scheduling--- and generation delays.------ Similarly, the monad instance of 'AsyncT' /may/ run each iteration--- concurrently based on demand. More concurrent iterations are started only--- if the previous iterations are not able to produce enough output for the--- consumer.------ @--- main = 'drain' . 'asyncly' $ do--- n <- return 3 \<\> return 2 \<\> return 1--- S.yieldM $ do--- threadDelay (n * 1000000)--- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)--- @--- @--- ThreadId 40: Delay 1--- ThreadId 39: Delay 2--- ThreadId 38: Delay 3--- @------ @since 0.1.0-newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}- deriving (MonadTrans)---- | A demand driven left biased parallely composing IO stream of elements of--- type @a@. See 'AsyncT' documentation for more details.------ @since 0.2.0-type Async = AsyncT IO---- | Fix the type of a polymorphic stream as 'AsyncT'.------ @since 0.1.0-asyncly :: IsStream t => AsyncT m a -> t m a-asyncly = adapt--instance IsStream AsyncT where- toStream = getAsyncT- fromStream = AsyncT- consM = consMAsync- (|:) = consMAsync----------------------------------------------------------------------------------- Semigroup----------------------------------------------------------------------------------- Monomorphically typed version of "async" for better performance of Semigroup--- instance.-{-# INLINE mappendAsync #-}-{-# SPECIALIZE mappendAsync :: AsyncT IO a -> AsyncT IO a -> AsyncT IO a #-}-mappendAsync :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a-mappendAsync m1 m2 = fromStream $ async (toStream m1) (toStream m2)--instance MonadAsync m => Semigroup (AsyncT m a) where- (<>) = mappendAsync----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance MonadAsync m => Monoid (AsyncT m a) where- mempty = K.nil- mappend = (<>)----------------------------------------------------------------------------------- Monad----------------------------------------------------------------------------------- GHC: if we change the implementation of bindWith with arguments in a--- different order we see a significant performance degradation (~2x).-{-# INLINE bindAsync #-}-{-# SPECIALIZE bindAsync :: AsyncT IO a -> (a -> AsyncT IO b) -> AsyncT IO b #-}-bindAsync :: MonadAsync m => AsyncT m a -> (a -> AsyncT m b) -> AsyncT m b-bindAsync m f = fromStream $ K.bindWith async (adapt m) (\a -> adapt $ f a)---- GHC: if we specify arguments in the definition of (>>=) we see a significant--- performance degradation (~2x).-instance MonadAsync m => Monad (AsyncT m) where- return = pure- (>>=) = bindAsync--{-# INLINE apAsync #-}-{-# SPECIALIZE apAsync :: AsyncT IO (a -> b) -> AsyncT IO a -> AsyncT IO b #-}-apAsync :: MonadAsync m => AsyncT m (a -> b) -> AsyncT m a -> AsyncT m b-apAsync mf m = ap (adapt mf) (adapt m)--instance (Monad m, MonadAsync m) => Applicative (AsyncT m) where- pure = AsyncT . K.yield- (<*>) = apAsync----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)----------------------------------------------------------------------------------- WAsyncT----------------------------------------------------------------------------------- | XXX we can implement it more efficienty by directly implementing instead--- of combining streams using wAsync.-{-# INLINE consMWAsync #-}-{-# SPECIALIZE consMWAsync :: IO a -> WAsyncT IO a -> WAsyncT IO a #-}-consMWAsync :: MonadAsync m => m a -> WAsyncT m a -> WAsyncT m a-consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)---- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.--- Merges two streams concurrently choosing elements from both fairly.------ @since 0.2.0-{-# INLINE wAsync #-}-wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-wAsync = joinStreamVarAsync WAsyncVar---- | The 'Semigroup' operation for 'WAsyncT' interleaves the elements from the--- two streams. Therefore, when @a <> b@ is evaluated, one action is picked--- from stream @a@ for evaluation and then the next action is picked from--- stream @b@ and then the next action is again picked from stream @a@, going--- around in a round-robin fashion. Many such actions are executed concurrently--- depending on 'maxThreads' and 'maxBuffer' limits. Results are served to the--- consumer in the order completion of the actions.------ Note that when multiple actions are combined like @a <> b <> c ... <> z@ we--- go in a round-robin fasion across all of them picking one action from each--- up to @z@ and then come back to @a@. Note that this operation cannot be--- used to fold a container of infinite streams as the state that it needs to--- maintain is proportional to the number of streams.------ @--- import "Streamly"--- import qualified "Streamly.Prelude" as S--- import Control.Concurrent------ main = (S.toList . 'wAsyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print--- @--- @--- [1,3,2,4]--- @------ Any exceptions generated by a constituent stream are propagated to the--- output stream. The output and exceptions from a single stream are guaranteed--- to arrive in the same order in the resulting stream as they were generated--- in the input stream. However, the relative ordering of elements from--- different streams in the resulting stream can vary depending on scheduling--- and generation delays.------ Similarly, the 'Monad' instance of 'WAsyncT' runs /all/ iterations fairly--- concurrently using a round robin scheduling.------ @--- main = 'drain' . 'wAsyncly' $ do--- n <- return 3 \<\> return 2 \<\> return 1--- S.yieldM $ do--- threadDelay (n * 1000000)--- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)--- @--- @--- ThreadId 40: Delay 1--- ThreadId 39: Delay 2--- ThreadId 38: Delay 3--- @------ @since 0.2.0-newtype WAsyncT m a = WAsyncT {getWAsyncT :: Stream m a}- deriving (MonadTrans)---- | A round robin parallely composing IO stream of elements of type @a@.--- See 'WAsyncT' documentation for more details.------ @since 0.2.0-type WAsync = WAsyncT IO---- | Fix the type of a polymorphic stream as 'WAsyncT'.------ @since 0.2.0-wAsyncly :: IsStream t => WAsyncT m a -> t m a-wAsyncly = adapt--instance IsStream WAsyncT where- toStream = getWAsyncT- fromStream = WAsyncT- consM = consMWAsync- (|:) = consMWAsync----------------------------------------------------------------------------------- Semigroup---------------------------------------------------------------------------------{-# INLINE mappendWAsync #-}-{-# SPECIALIZE mappendWAsync :: WAsyncT IO a -> WAsyncT IO a -> WAsyncT IO a #-}-mappendWAsync :: MonadAsync m => WAsyncT m a -> WAsyncT m a -> WAsyncT m a-mappendWAsync m1 m2 = fromStream $ wAsync (toStream m1) (toStream m2)--instance MonadAsync m => Semigroup (WAsyncT m a) where- (<>) = mappendWAsync----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance MonadAsync m => Monoid (WAsyncT m a) where- mempty = K.nil- mappend = (<>)----------------------------------------------------------------------------------- Monad----------------------------------------------------------------------------------- GHC: if we change the implementation of bindWith with arguments in a--- different order we see a significant performance degradation (~2x).-{-# INLINE bindWAsync #-}-{-# SPECIALIZE bindWAsync :: WAsyncT IO a -> (a -> WAsyncT IO b) -> WAsyncT IO b #-}-bindWAsync :: MonadAsync m => WAsyncT m a -> (a -> WAsyncT m b) -> WAsyncT m b-bindWAsync m f = fromStream $ K.bindWith wAsync (adapt m) (\a -> adapt $ f a)---- GHC: if we specify arguments in the definition of (>>=) we see a significant--- performance degradation (~2x).-instance MonadAsync m => Monad (WAsyncT m) where- return = pure- (>>=) = bindWAsync--{-# INLINE apWAsync #-}-{-# SPECIALIZE apWAsync :: WAsyncT IO (a -> b) -> WAsyncT IO a -> WAsyncT IO b #-}-apWAsync :: MonadAsync m => WAsyncT m (a -> b) -> WAsyncT m a -> WAsyncT m b-apWAsync mf m = ap (adapt mf) (adapt m)--instance (Monad m, MonadAsync m) => Applicative (WAsyncT m) where- pure = WAsyncT . K.yield- (<*>) = apWAsync----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------MONAD_COMMON_INSTANCES(WAsyncT, MONADPARALLEL)
− src/Streamly/Streams/Combinators.hs
@@ -1,217 +0,0 @@-{-# LANGUAGE CPP #-}--#include "inline.hs"---- |--- Module : Streamly.Streams.Combinators--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Combinators- ( maxThreads- , maxBuffer- , maxYields- , rate- , avgRate- , minRate- , maxRate- , constRate- , inspectMode- , printState- )-where--import Control.Monad.IO.Class (MonadIO(liftIO))-import Data.Int (Int64)--import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK-import Streamly.Streams.Serial (SerialT)------------------------------------------------------------------------------------ Concurrency control-------------------------------------------------------------------------------------- XXX need to write these in direct style otherwise they will break fusion.------ | Specify the maximum number of threads that can be spawned concurrently for--- any concurrent combinator in a stream.--- A value of 0 resets the thread limit to default, a negative value means--- there is no limit. The default value is 1500. 'maxThreads' does not affect--- 'ParallelT' streams as they can use unbounded number of threads.------ When the actions in a stream are IO bound, having blocking IO calls, this--- option can be used to control the maximum number of in-flight IO requests.--- When the actions are CPU bound this option can be used to--- control the amount of CPU used by the stream.------ @since 0.4.0-{-# INLINE_NORMAL maxThreads #-}-maxThreads :: IsStream t => Int -> t m a -> t m a-maxThreads n m = mkStream $ \st stp sng yld ->- foldStreamShared (setMaxThreads n st) stp sng yld m--{--{-# RULES "maxThreadsSerial serial" maxThreads = maxThreadsSerial #-}-maxThreadsSerial :: Int -> SerialT m a -> SerialT m a-maxThreadsSerial _ = id--}---- | Specify the maximum size of the buffer for storing the results from--- concurrent computations. If the buffer becomes full we stop spawning more--- concurrent tasks until there is space in the buffer.--- A value of 0 resets the buffer size to default, a negative value means--- there is no limit. The default value is 1500.------ CAUTION! using an unbounded 'maxBuffer' value (i.e. a negative value)--- coupled with an unbounded 'maxThreads' value is a recipe for disaster in--- presence of infinite streams, or very large streams. Especially, it must--- not be used when 'pure' is used in 'ZipAsyncM' streams as 'pure' in--- applicative zip streams generates an infinite stream causing unbounded--- concurrent generation with no limit on the buffer or threads.------ @since 0.4.0-{-# INLINE_NORMAL maxBuffer #-}-maxBuffer :: IsStream t => Int -> t m a -> t m a-maxBuffer n m = mkStream $ \st stp sng yld ->- foldStreamShared (setMaxBuffer n st) stp sng yld m--{--{-# RULES "maxBuffer serial" maxBuffer = maxBufferSerial #-}-maxBufferSerial :: Int -> SerialT m a -> SerialT m a-maxBufferSerial _ = id--}---- | Specify the pull rate of a stream.--- A 'Nothing' value resets the rate to default which is unlimited. When the--- rate is specified, concurrent production may be ramped up or down--- automatically to achieve the specified yield rate. The specific behavior for--- different styles of 'Rate' specifications is documented under 'Rate'. The--- effective maximum production rate achieved by a stream is governed by:------ * The 'maxThreads' limit--- * The 'maxBuffer' limit--- * The maximum rate that the stream producer can achieve--- * The maximum rate that the stream consumer can achieve------ @since 0.5.0-{-# INLINE_NORMAL rate #-}-rate :: IsStream t => Maybe Rate -> t m a -> t m a-rate r m = mkStream $ \st stp sng yld ->- case r of- Just (Rate low goal _ _) | goal < low ->- error "rate: Target rate cannot be lower than minimum rate."- Just (Rate _ goal high _) | goal > high ->- error "rate: Target rate cannot be greater than maximum rate."- Just (Rate low _ high _) | low > high ->- error "rate: Minimum rate cannot be greater than maximum rate."- _ -> foldStreamShared (setStreamRate r st) stp sng yld m---- XXX implement for serial streams as well, as a simple delay--{--{-# RULES "rate serial" rate = yieldRateSerial #-}-yieldRateSerial :: Double -> SerialT m a -> SerialT m a-yieldRateSerial _ = id--}---- | Same as @rate (Just $ Rate (r/2) r (2*r) maxBound)@------ Specifies the average production rate of a stream in number of yields--- per second (i.e. @Hertz@). Concurrent production is ramped up or down--- automatically to achieve the specified average yield rate. The rate can--- go down to half of the specified rate on the lower side and double of--- the specified rate on the higher side.------ @since 0.5.0-avgRate :: IsStream t => Double -> t m a -> t m a-avgRate r = rate (Just $ Rate (r/2) r (2*r) maxBound)---- | Same as @rate (Just $ Rate r r (2*r) maxBound)@------ Specifies the minimum rate at which the stream should yield values. As--- far as possible the yield rate would never be allowed to go below the--- specified rate, even though it may possibly go above it at times, the--- upper limit is double of the specified rate.------ @since 0.5.0-minRate :: IsStream t => Double -> t m a -> t m a-minRate r = rate (Just $ Rate r r (2*r) maxBound)---- | Same as @rate (Just $ Rate (r/2) r r maxBound)@------ Specifies the maximum rate at which the stream should yield values. As--- far as possible the yield rate would never be allowed to go above the--- specified rate, even though it may possibly go below it at times, the--- lower limit is half of the specified rate. This can be useful in--- applications where certain resource usage must not be allowed to go--- beyond certain limits.------ @since 0.5.0-maxRate :: IsStream t => Double -> t m a -> t m a-maxRate r = rate (Just $ Rate (r/2) r r maxBound)---- | Same as @rate (Just $ Rate r r r 0)@------ Specifies a constant yield rate. If for some reason the actual rate--- goes above or below the specified rate we do not try to recover it by--- increasing or decreasing the rate in future. This can be useful in--- applications like graphics frame refresh where we need to maintain a--- constant refresh rate.------ @since 0.5.0-constRate :: IsStream t => Double -> t m a -> t m a-constRate r = rate (Just $ Rate r r r 0)---- | Specify the average latency, in nanoseconds, of a single threaded action--- in a concurrent composition. Streamly can measure the latencies, but that is--- possible only after at least one task has completed. This combinator can be--- used to provide a latency hint so that rate control using 'rate' can take--- that into account right from the beginning. When not specified then a--- default behavior is chosen which could be too slow or too fast, and would be--- restricted by any other control parameters configured.--- A value of 0 indicates default behavior, a negative value means there is no--- limit i.e. zero latency.--- This would normally be useful only in high latency and high throughput--- cases.----{-# INLINE_NORMAL _serialLatency #-}-_serialLatency :: IsStream t => Int -> t m a -> t m a-_serialLatency n m = mkStream $ \st stp sng yld ->- foldStreamShared (setStreamLatency n st) stp sng yld m--{--{-# RULES "serialLatency serial" _serialLatency = serialLatencySerial #-}-serialLatencySerial :: Int -> SerialT m a -> SerialT m a-serialLatencySerial _ = id--}---- Stop concurrent dispatches after this limit. This is useful in API's like--- "take" where we want to dispatch only upto the number of elements "take"--- needs. This value applies only to the immediate next level and is not--- inherited by everything in enclosed scope.-{-# INLINE_NORMAL maxYields #-}-maxYields :: IsStream t => Maybe Int64 -> t m a -> t m a-maxYields n m = mkStream $ \st stp sng yld ->- foldStreamShared (setYieldLimit n st) stp sng yld m--{-# RULES "maxYields serial" maxYields = maxYieldsSerial #-}-maxYieldsSerial :: Maybe Int64 -> SerialT m a -> SerialT m a-maxYieldsSerial _ = id--printState :: MonadIO m => State Stream m a -> m ()-printState st = liftIO $ do- let msv = streamVar st- case msv of- Just sv -> dumpSVar sv >>= putStrLn- Nothing -> putStrLn "No SVar"---- | Print debug information about an SVar when the stream ends-inspectMode :: IsStream t => t m a -> t m a-inspectMode m = mkStream $ \st stp sng yld ->- foldStreamShared (setInspectMode st) stp sng yld m
− src/Streamly/Streams/Enumeration.hs
@@ -1,550 +0,0 @@-{-# LANGUAGE CPP #-}---- |--- Module : Streamly.Streams.Enumeration--- Copyright : (c) 2018 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ The functions defined in this module should be rarely needed for direct use,--- try to use the operations from the 'Enumerable' type class--- instances instead.------ This module provides an 'Enumerable' type class to enumerate 'Enum' types--- into a stream. The operations in this type class correspond to similar--- perations in the 'Enum' type class, the only difference is that they produce--- a stream instead of a list. These operations cannot be defined generically--- based on the 'Enum' type class. We provide instances for commonly used--- types. If instances for other types are needed convenience functions defined--- in this module can be used to define them. Alternatively, these functions--- can be used directly.--module Streamly.Streams.Enumeration- (- Enumerable (..)-- -- ** Enumerating 'Bounded' 'Enum' Types- , enumerate- , enumerateTo- , enumerateFromBounded-- -- ** Enumerating 'Enum' Types not larger than 'Int'- , enumerateFromToSmall- , enumerateFromThenToSmall- , enumerateFromThenSmallBounded-- -- ** Enumerating 'Bounded' 'Integral' Types- , enumerateFromIntegral- , enumerateFromThenIntegral-- -- ** Enumerating 'Integral' Types- , enumerateFromToIntegral- , enumerateFromThenToIntegral-- -- ** Enumerating unbounded 'Integral' Types- , enumerateFromStepIntegral-- -- ** Enumerating 'Fractional' Types- , enumerateFromFractional- , enumerateFromToFractional- , enumerateFromThenFractional- , enumerateFromThenToFractional- )-where--import Data.Fixed-import Data.Int-import Data.Ratio-import Data.Word-import Numeric.Natural-import Data.Functor.Identity (Identity(..))--import Streamly.Streams.StreamD (fromStreamD)-import Streamly.Streams.StreamK (IsStream(..))--import qualified Streamly.Streams.StreamD as D-import qualified Streamly.Streams.Serial as Serial------------------------------------------------------------------------------------ Enumeration of Integral types-------------------------------------------------------------------------------------- | @enumerateFromStepIntegral from step@ generates an infinite stream whose--- first element is @from@ and the successive elements are in increments of--- @step@.------ CAUTION: This function is not safe for finite integral types. It does not--- check for overflow, underflow or bounds.------ @--- > S.toList $ S.take 4 $ S.enumerateFromStepIntegral 0 2--- [0,2,4,6]--- > S.toList $ S.take 3 $ S.enumerateFromStepIntegral 0 (-2)--- [0,-2,-4]--- @------ @since 0.6.0-{-# INLINE enumerateFromStepIntegral #-}-enumerateFromStepIntegral- :: (IsStream t, Monad m, Integral a)- => a -> a -> t m a-enumerateFromStepIntegral from stride =- fromStreamD $ D.enumerateFromStepIntegral from stride---- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a--- stream whose first element is @from@ and the successive elements are in--- increments of @1@. The stream is bounded by the size of the 'Integral' type.------ @--- > S.toList $ S.take 4 $ S.enumerateFromIntegral (0 :: Int)--- [0,1,2,3]--- @------ @since 0.6.0-{-# INLINE enumerateFromIntegral #-}-enumerateFromIntegral- :: (IsStream t, Monad m, Integral a, Bounded a)- => a -> t m a-enumerateFromIntegral from = fromStreamD $ D.enumerateFromIntegral from---- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from--- then@ generates a stream whose first element is @from@, the second element--- is @then@ and the successive elements are in increments of @then - from@.--- The stream is bounded by the size of the 'Integral' type.------ @--- > S.toList $ S.take 4 $ S.enumerateFromThenIntegral (0 :: Int) 2--- [0,2,4,6]--- > S.toList $ S.take 4 $ S.enumerateFromThenIntegral (0 :: Int) (-2)--- [0,-2,-4,-6]--- @------ @since 0.6.0-{-# INLINE enumerateFromThenIntegral #-}-enumerateFromThenIntegral- :: (IsStream t, Monad m, Integral a, Bounded a)- => a -> a -> t m a-enumerateFromThenIntegral from next =- fromStreamD $ D.enumerateFromThenIntegral from next---- | Enumerate an 'Integral' type up to a given limit.--- @enumerateFromToIntegral from to@ generates a finite stream whose first--- element is @from@ and successive elements are in increments of @1@ up to--- @to@.------ @--- > S.toList $ S.enumerateFromToIntegral 0 4--- [0,1,2,3,4]--- @------ @since 0.6.0-{-# INLINE enumerateFromToIntegral #-}-enumerateFromToIntegral :: (IsStream t, Monad m, Integral a) => a -> a -> t m a-enumerateFromToIntegral from to =- fromStreamD $ D.enumerateFromToIntegral from to---- | Enumerate an 'Integral' type in steps up to a given limit.--- @enumerateFromThenToIntegral from then to@ generates a finite stream whose--- first element is @from@, the second element is @then@ and the successive--- elements are in increments of @then - from@ up to @to@.------ @--- > S.toList $ S.enumerateFromThenToIntegral 0 2 6--- [0,2,4,6]--- > S.toList $ S.enumerateFromThenToIntegral 0 (-2) (-6)--- [0,-2,-4,-6]--- @------ @since 0.6.0-{-# INLINE enumerateFromThenToIntegral #-}-enumerateFromThenToIntegral- :: (IsStream t, Monad m, Integral a)- => a -> a -> a -> t m a-enumerateFromThenToIntegral from next to =- fromStreamD $ D.enumerateFromThenToIntegral from next to------------------------------------------------------------------------------------ Enumeration of Fractional types-------------------------------------------------------------------------------------- Even though the underlying implementation of enumerateFromFractional and--- enumerateFromThenFractional works for any 'Num' we have restricted these to--- 'Fractional' because these do not perform any bounds check, in contrast to--- integral versions and are therefore not equivalent substitutes for those.------ | Numerically stable enumeration from a 'Fractional' number in steps of size--- @1@. @enumerateFromFractional from@ generates a stream whose first element--- is @from@ and the successive elements are in increments of @1@. No overflow--- or underflow checks are performed.------ This is the equivalent to 'enumFrom' for 'Fractional' types. For example:------ @--- > S.toList $ S.take 4 $ S.enumerateFromFractional 1.1--- [1.1,2.1,3.1,4.1]--- @--------- @since 0.6.0-{-# INLINE enumerateFromFractional #-}-enumerateFromFractional :: (IsStream t, Monad m, Fractional a) => a -> t m a-enumerateFromFractional from = fromStreamD $ D.numFrom from---- | Numerically stable enumeration from a 'Fractional' number in steps.--- @enumerateFromThenFractional from then@ generates a stream whose first--- element is @from@, the second element is @then@ and the successive elements--- are in increments of @then - from@. No overflow or underflow checks are--- performed.------ This is the equivalent of 'enumFromThen' for 'Fractional' types. For--- example:------ @--- > S.toList $ S.take 4 $ S.enumerateFromThenFractional 1.1 2.1--- [1.1,2.1,3.1,4.1]--- > S.toList $ S.take 4 $ S.enumerateFromThenFractional 1.1 (-2.1)--- [1.1,-2.1,-5.300000000000001,-8.500000000000002]--- @------ @since 0.6.0-{-# INLINE enumerateFromThenFractional #-}-enumerateFromThenFractional- :: (IsStream t, Monad m, Fractional a)- => a -> a -> t m a-enumerateFromThenFractional from next = fromStreamD $ D.numFromThen from next---- | Numerically stable enumeration from a 'Fractional' number to a given--- limit. @enumerateFromToFractional from to@ generates a finite stream whose--- first element is @from@ and successive elements are in increments of @1@ up--- to @to@.------ This is the equivalent of 'enumFromTo' for 'Fractional' types. For--- example:------ @--- > S.toList $ S.enumerateFromToFractional 1.1 4--- [1.1,2.1,3.1,4.1]--- > S.toList $ S.enumerateFromToFractional 1.1 4.6--- [1.1,2.1,3.1,4.1,5.1]--- @------ Notice that the last element is equal to the specified @to@ value after--- rounding to the nearest integer.------ @since 0.6.0-{-# INLINE enumerateFromToFractional #-}-enumerateFromToFractional- :: (IsStream t, Monad m, Fractional a, Ord a)- => a -> a -> t m a-enumerateFromToFractional from to =- fromStreamD $ D.enumerateFromToFractional from to---- | Numerically stable enumeration from a 'Fractional' number in steps up to a--- given limit. @enumerateFromThenToFractional from then to@ generates a--- finite stream whose first element is @from@, the second element is @then@--- and the successive elements are in increments of @then - from@ up to @to@.------ This is the equivalent of 'enumFromThenTo' for 'Fractional' types. For--- example:------ @--- > S.toList $ S.enumerateFromThenToFractional 0.1 2 6--- [0.1,2.0,3.9,5.799999999999999]--- > S.toList $ S.enumerateFromThenToFractional 0.1 (-2) (-6)--- [0.1,-2.0,-4.1000000000000005,-6.200000000000001]--- @--------- @since 0.6.0-{-# INLINE enumerateFromThenToFractional #-}-enumerateFromThenToFractional- :: (IsStream t, Monad m, Fractional a, Ord a)- => a -> a -> a -> t m a-enumerateFromThenToFractional from next to =- fromStreamD $ D.enumerateFromThenToFractional from next to------------------------------------------------------------------------------------ Enumeration of Enum types not larger than Int-------------------------------------------------------------------------------------- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'.------ @since 0.6.0-{-# INLINE enumerateFromToSmall #-}-enumerateFromToSmall :: (IsStream t, Monad m, Enum a) => a -> a -> t m a-enumerateFromToSmall from to = Serial.map toEnum $- enumerateFromToIntegral (fromEnum from) (fromEnum to)---- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'.------ @since 0.6.0-{-# INLINE enumerateFromThenToSmall #-}-enumerateFromThenToSmall :: (IsStream t, Monad m, Enum a)- => a -> a -> a -> t m a-enumerateFromThenToSmall from next to = Serial.map toEnum $- enumerateFromThenToIntegral (fromEnum from) (fromEnum next) (fromEnum to)---- | 'enumerateFromThen' for 'Enum' types not larger than 'Int'.------ Note: We convert the 'Enum' to 'Int' and enumerate the 'Int'. If a--- type is bounded but does not have a 'Bounded' instance then we can go on--- enumerating it beyond the legal values of the type, resulting in the failure--- of 'toEnum' when converting back to 'Enum'. Therefore we require a 'Bounded'--- instance for this function to be safely used.------ @since 0.6.0-{-# INLINE enumerateFromThenSmallBounded #-}-enumerateFromThenSmallBounded :: (IsStream t, Monad m, Enumerable a, Bounded a)- => a -> a -> t m a-enumerateFromThenSmallBounded from next =- case fromEnum next >= fromEnum from of- True -> enumerateFromThenTo from next maxBound- False -> enumerateFromThenTo from next minBound------------------------------------------------------------------------------------ Enumerable type class-------------------------------------------------------------------------------------- NOTE: We would like to rewrite calls to fromList [1..] etc. to stream--- enumerations like this:------ {-# RULES "fromList enumFrom" [1]--- forall (a :: Int). D.fromList (enumFrom a) = D.enumerateFromIntegral a #-}------ But this does not work because enumFrom is a class method and GHC rewrites--- it quickly, so we do not get a chance to have our rule fired.---- | Types that can be enumerated as a stream. The operations in this type--- class are equivalent to those in the 'Enum' type class, except that these--- generate a stream instead of a list. Use the functions in--- "Streamly.Streams.Enumeration" module to define new instances.------ @since 0.6.0-class Enum a => Enumerable a where- -- | @enumerateFrom from@ generates a stream starting with the element- -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or- -- generating an infinite stream when the type is not 'Bounded'.- --- -- @- -- > S.toList $ S.take 4 $ S.enumerateFrom (0 :: Int)- -- [0,1,2,3]- -- @- --- -- For 'Fractional' types, enumeration is numerically stable. However, no- -- overflow or underflow checks are performed.- --- -- @- -- > S.toList $ S.take 4 $ S.enumerateFrom 1.1- -- [1.1,2.1,3.1,4.1]- -- @- --- -- @since 0.6.0- enumerateFrom :: (IsStream t, Monad m) => a -> t m a-- -- | Generate a finite stream starting with the element @from@, enumerating- -- the type up to the value @to@. If @to@ is smaller than @from@ then an- -- empty stream is returned.- --- -- @- -- > S.toList $ S.enumerateFromTo 0 4- -- [0,1,2,3,4]- -- @- --- -- For 'Fractional' types, the last element is equal to the specified @to@- -- value after rounding to the nearest integral value.- --- -- @- -- > S.toList $ S.enumerateFromTo 1.1 4- -- [1.1,2.1,3.1,4.1]- -- > S.toList $ S.enumerateFromTo 1.1 4.6- -- [1.1,2.1,3.1,4.1,5.1]- -- @- --- -- @since 0.6.0- enumerateFromTo :: (IsStream t, Monad m) => a -> a -> t m a-- -- | @enumerateFromThen from then@ generates a stream whose first element- -- is @from@, the second element is @then@ and the successive elements are- -- in increments of @then - from@. Enumeration can occur downwards or- -- upwards depending on whether @then@ comes before or after @from@. For- -- 'Bounded' types the stream ends when 'maxBound' is reached, for- -- unbounded types it keeps enumerating infinitely.- --- -- @- -- > S.toList $ S.take 4 $ S.enumerateFromThen 0 2- -- [0,2,4,6]- -- > S.toList $ S.take 4 $ S.enumerateFromThen 0 (-2)- -- [0,-2,-4,-6]- -- @- --- -- @since 0.6.0- enumerateFromThen :: (IsStream t, Monad m) => a -> a -> t m a-- -- | @enumerateFromThenTo from then to@ generates a finite stream whose- -- first element is @from@, the second element is @then@ and the successive- -- elements are in increments of @then - from@ up to @to@. Enumeration can- -- occur downwards or upwards depending on whether @then@ comes before or- -- after @from@.- --- -- @- -- > S.toList $ S.enumerateFromThenTo 0 2 6- -- [0,2,4,6]- -- > S.toList $ S.enumerateFromThenTo 0 (-2) (-6)- -- [0,-2,-4,-6]- -- @- --- -- @since 0.6.0- enumerateFromThenTo :: (IsStream t, Monad m) => a -> a -> a -> t m a---- MAYBE: Sometimes it is more convenient to know the count rather then the--- ending or starting element. For those cases we can define the folllowing--- APIs. All of these will work only for bounded types if we represent the--- count by Int.------ enumerateN--- enumerateFromN--- enumerateToN--- enumerateFromStep--- enumerateFromStepN------------------------------------------------------------------------------------ Convenient functions for bounded types-------------------------------------------------------------------------------------- |--- > enumerate = enumerateFrom minBound------ Enumerate a 'Bounded' type from its 'minBound' to 'maxBound'------ @since 0.6.0-{-# INLINE enumerate #-}-enumerate :: (IsStream t, Monad m, Bounded a, Enumerable a) => t m a-enumerate = enumerateFrom minBound---- |--- > enumerateTo = enumerateFromTo minBound------ Enumerate a 'Bounded' type from its 'minBound' to specified value.------ @since 0.6.0-{-# INLINE enumerateTo #-}-enumerateTo :: (IsStream t, Monad m, Bounded a, Enumerable a) => a -> t m a-enumerateTo = enumerateFromTo minBound---- |--- > enumerateFromBounded = enumerateFromTo from maxBound------ 'enumerateFrom' for 'Bounded' 'Enum' types.------ @since 0.6.0-{-# INLINE enumerateFromBounded #-}-enumerateFromBounded :: (IsStream t, Monad m, Enumerable a, Bounded a)- => a -> t m a-enumerateFromBounded from = enumerateFromTo from maxBound------------------------------------------------------------------------------------ Enumerable Instances-------------------------------------------------------------------------------------- For Enum types smaller than or equal to Int size.-#define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \-instance Enumerable SMALL_TYPE where { \- {-# INLINE enumerateFrom #-}; \- enumerateFrom = enumerateFromBounded; \- {-# INLINE enumerateFromThen #-}; \- enumerateFromThen = enumerateFromThenSmallBounded; \- {-# INLINE enumerateFromTo #-}; \- enumerateFromTo = enumerateFromToSmall; \- {-# INLINE enumerateFromThenTo #-}; \- enumerateFromThenTo = enumerateFromThenToSmall }---ENUMERABLE_BOUNDED_SMALL(())-ENUMERABLE_BOUNDED_SMALL(Bool)-ENUMERABLE_BOUNDED_SMALL(Ordering)-ENUMERABLE_BOUNDED_SMALL(Char)---- For bounded Integral Enum types, may be larger than Int.-#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \-instance Enumerable INTEGRAL_TYPE where { \- {-# INLINE enumerateFrom #-}; \- enumerateFrom = enumerateFromIntegral; \- {-# INLINE enumerateFromThen #-}; \- enumerateFromThen = enumerateFromThenIntegral; \- {-# INLINE enumerateFromTo #-}; \- enumerateFromTo = enumerateFromToIntegral; \- {-# INLINE enumerateFromThenTo #-}; \- enumerateFromThenTo = enumerateFromThenToIntegral }--ENUMERABLE_BOUNDED_INTEGRAL(Int)-ENUMERABLE_BOUNDED_INTEGRAL(Int8)-ENUMERABLE_BOUNDED_INTEGRAL(Int16)-ENUMERABLE_BOUNDED_INTEGRAL(Int32)-ENUMERABLE_BOUNDED_INTEGRAL(Int64)-ENUMERABLE_BOUNDED_INTEGRAL(Word)-ENUMERABLE_BOUNDED_INTEGRAL(Word8)-ENUMERABLE_BOUNDED_INTEGRAL(Word16)-ENUMERABLE_BOUNDED_INTEGRAL(Word32)-ENUMERABLE_BOUNDED_INTEGRAL(Word64)---- For unbounded Integral Enum types.-#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \-instance Enumerable INTEGRAL_TYPE where { \- {-# INLINE enumerateFrom #-}; \- enumerateFrom from = enumerateFromStepIntegral from 1; \- {-# INLINE enumerateFromThen #-}; \- enumerateFromThen from next = \- enumerateFromStepIntegral from (next - from); \- {-# INLINE enumerateFromTo #-}; \- enumerateFromTo = enumerateFromToIntegral; \- {-# INLINE enumerateFromThenTo #-}; \- enumerateFromThenTo = enumerateFromThenToIntegral }--ENUMERABLE_UNBOUNDED_INTEGRAL(Integer)-ENUMERABLE_UNBOUNDED_INTEGRAL(Natural)--#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \-instance (CONSTRAINT) => Enumerable (FRACTIONAL_TYPE) where { \- {-# INLINE enumerateFrom #-}; \- enumerateFrom = enumerateFromFractional; \- {-# INLINE enumerateFromThen #-}; \- enumerateFromThen = enumerateFromThenFractional; \- {-# INLINE enumerateFromTo #-}; \- enumerateFromTo = enumerateFromToFractional; \- {-# INLINE enumerateFromThenTo #-}; \- enumerateFromThenTo = enumerateFromThenToFractional }--ENUMERABLE_FRACTIONAL(Float,)-ENUMERABLE_FRACTIONAL(Double,)-ENUMERABLE_FRACTIONAL(Fixed a,HasResolution a)-ENUMERABLE_FRACTIONAL(Ratio a,Integral a)--#if __GLASGOW_HASKELL__ >= 800-instance Enumerable a => Enumerable (Identity a) where- {-# INLINE enumerateFrom #-}- enumerateFrom (Identity from) = Serial.map Identity $- enumerateFrom from- {-# INLINE enumerateFromThen #-}- enumerateFromThen (Identity from) (Identity next) = Serial.map Identity $- enumerateFromThen from next- {-# INLINE enumerateFromTo #-}- enumerateFromTo (Identity from) (Identity to) = Serial.map Identity $- enumerateFromTo from to- {-# INLINE enumerateFromThenTo #-}- enumerateFromThenTo (Identity from) (Identity next) (Identity to) =- Serial.map Identity $ enumerateFromThenTo from next to-#endif---- TODO-{--instance Enumerable a => Enumerable (Last a)-instance Enumerable a => Enumerable (First a)-instance Enumerable a => Enumerable (Max a)-instance Enumerable a => Enumerable (Min a)-instance Enumerable a => Enumerable (Const a b)-instance Enumerable (f a) => Enumerable (Alt f a)-instance Enumerable (f a) => Enumerable (Ap f a)--}
− src/Streamly/Streams/Instances.hs
@@ -1,134 +0,0 @@---------------------------------------------------------------------------------- CPP macros for common instances----------------------------------------------------------------------------------- XXX use template haskell instead and include Monoid and IsStream instances--- as well.--#define MONADPARALLEL , MonadAsync m--#define MONAD_APPLICATIVE_INSTANCE(STREAM,CONSTRAINT) \-instance (Monad m CONSTRAINT) => Applicative (STREAM m) where { \- {-# INLINE pure #-}; \- pure = STREAM . K.yield; \- {-# INLINE (<*>) #-}; \- (<*>) = ap }--#define MONAD_COMMON_INSTANCES(STREAM,CONSTRAINT) \-instance Monad m => Functor (STREAM m) where { \- fmap = map }; \- \-instance (MonadBase b m, Monad m CONSTRAINT) => MonadBase b (STREAM m) where {\- liftBase = liftBaseDefault }; \- \-instance (MonadIO m CONSTRAINT) => MonadIO (STREAM m) where { \- liftIO = lift . liftIO }; \- \-instance (MonadThrow m CONSTRAINT) => MonadThrow (STREAM m) where { \- throwM = lift . throwM }; \- \-{- \-instance (MonadError e m CONSTRAINT) => MonadError e (STREAM m) where { \- throwError = lift . throwError; \- catchError m h = \- fromStream $ withCatchError (toStream m) (\e -> toStream $ h e) }; \--} \- \-instance (MonadReader r m CONSTRAINT) => MonadReader r (STREAM m) where { \- ask = lift ask; \- local f m = fromStream $ K.withLocal f (toStream m) }; \- \-instance (MonadState s m CONSTRAINT) => MonadState s (STREAM m) where { \- get = lift get; \- put x = lift (put x); \- state k = lift (state k) }----------------------------------------------------------------------------------- Lists----------------------------------------------------------------------------------- Serial streams can act like regular lists using the Identity monad---- XXX Show instance is 10x slower compared to read, we can do much better.--- The list show instance itself is really slow.---- XXX The default definitions of "<" in the Ord instance etc. do not perform--- well, because they do not get inlined. Need to add INLINE in Ord class in--- base?--#if MIN_VERSION_deepseq(1,4,3)-#define NFDATA1_INSTANCE(STREAM) \-instance NFData1 (STREAM Identity) where { \- {-# INLINE liftRnf #-}; \- liftRnf r = runIdentity . P.foldl' (\_ x -> r x) () }-#else-#define NFDATA1_INSTANCE(STREAM)-#endif--#define LIST_INSTANCES(STREAM) \-instance IsList (STREAM Identity a) where { \- type (Item (STREAM Identity a)) = a; \- {-# INLINE fromList #-}; \- fromList = P.fromList; \- {-# INLINE toList #-}; \- toList = runIdentity . P.toList }; \- \-instance Eq a => Eq (STREAM Identity a) where { \- {-# INLINE (==) #-}; \- (==) xs ys = runIdentity $ P.eqBy (==) xs ys }; \- \-instance Ord a => Ord (STREAM Identity a) where { \- {-# INLINE compare #-}; \- compare xs ys = runIdentity $ P.cmpBy compare xs ys; \- {-# INLINE (<) #-}; \- x < y = case compare x y of { LT -> True; _ -> False }; \- {-# INLINE (<=) #-}; \- x <= y = case compare x y of { GT -> False; _ -> True }; \- {-# INLINE (>) #-}; \- x > y = case compare x y of { GT -> True; _ -> False }; \- {-# INLINE (>=) #-}; \- x >= y = case compare x y of { LT -> False; _ -> True }; \- {-# INLINE max #-}; \- max x y = if x <= y then y else x; \- {-# INLINE min #-}; \- min x y = if x <= y then x else y; }; \- \-instance Show a => Show (STREAM Identity a) where { \- showsPrec p dl = showParen (p > 10) $ \- showString "fromList " . shows (toList dl) }; \- \-instance Read a => Read (STREAM Identity a) where { \- readPrec = parens $ prec 10 $ do { \- Ident "fromList" <- lexP; \- fromList <$> readPrec }; \- readListPrec = readListPrecDefault }; \- \-instance (a ~ Char) => IsString (STREAM Identity a) where { \- {-# INLINE fromString #-}; \- fromString = P.fromList }; \- \-instance NFData a => NFData (STREAM Identity a) where { \- {-# INLINE rnf #-}; \- rnf = runIdentity . P.foldl' (\_ x -> rnf x) () }; \------------------------------------------------------------------------------------ Foldable------------------------------------------------------------------------------------ XXX the foldable instance seems to be quit slow. We can try writing--- custom implementations of foldr and foldl'. If nothing works we can also try--- writing a Foldable for Identity monad rather than for "Foldable m".-#define FOLDABLE_INSTANCE(STREAM) \-instance (Foldable m, Monad m) => Foldable (STREAM m) where { \- {-# INLINE foldMap #-}; \- foldMap f = fold . P.foldr mappend mempty . fmap f }------------------------------------------------------------------------------------ Traversable----------------------------------------------------------------------------------#define TRAVERSABLE_INSTANCE(STREAM) \-instance Traversable (STREAM Identity) where { \- {-# INLINE traverse #-}; \- traverse f s = runIdentity $ P.foldr consA (pure mempty) s \- where { consA x ys = liftA2 K.cons (f x) ys }}
− src/Streamly/Streams/Parallel.hs
@@ -1,511 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving#-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX---- |--- Module : Streamly.Streams.Parallel--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Parallel- (- ParallelT- , Parallel- , parallely- , parallel- , parallelFst- , parallelMin- , tapAsync-- -- * Function application- , mkParallel- , (|$)- , (|&)- , (|$.)- , (|&.)- )-where--import Control.Concurrent (myThreadId)-import Control.Exception (SomeException(..), throwIO)-import Control.Monad (ap)-import Control.Monad.Base (MonadBase(..), liftBaseDefault)-import Control.Monad.Catch (MonadThrow, throwM)--- import Control.Monad.Error.Class (MonadError(..))-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader.Class (MonadReader(..))-import Control.Monad.State.Class (MonadState(..))-import Control.Monad.Trans.Class (MonadTrans(lift))-import Data.Functor (void)-import Data.IORef (readIORef, writeIORef)-import Data.Maybe (fromJust)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif-import Prelude hiding (map)--import qualified Data.Set as Set--import Streamly.Streams.SVar (fromSVar, fromStreamVar)-import Streamly.Streams.Serial (map)-import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,- foldStreamShared, adapt)-import qualified Streamly.Streams.StreamK as K--#include "Instances.hs"------------------------------------------------------------------------------------ Parallel----------------------------------------------------------------------------------{-# NOINLINE runOne #-}-runOne- :: MonadIO m- => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()-runOne st m0 winfo =- case getYieldLimit st of- Nothing -> go m0- Just _ -> runOneLimited st m0 winfo-- where-- go m = do- liftIO $ decrementBufferLimit sv- foldStreamShared st yieldk single stop m-- sv = fromJust $ streamVar st-- stop = liftIO $ do- incrementBufferLimit sv- sendStop sv winfo- sendit a = liftIO $ void $ send sv (ChildYield a)- single a = sendit a >> (liftIO $ sendStop sv winfo)- yieldk a r = sendit a >> go r--runOneLimited- :: MonadIO m- => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()-runOneLimited st m0 winfo = go m0-- where-- go m = do- yieldLimitOk <- liftIO $ decrementYieldLimit sv- if yieldLimitOk- then do- liftIO $ decrementBufferLimit sv- foldStreamShared st yieldk single stop m- else do- liftIO $ cleanupSVarFromWorker sv- liftIO $ sendStop sv winfo-- sv = fromJust $ streamVar st-- stop = liftIO $ do- incrementBufferLimit sv- incrementYieldLimit sv- sendStop sv winfo- sendit a = liftIO $ void $ send sv (ChildYield a)- single a = sendit a >> (liftIO $ sendStop sv winfo)- yieldk a r = sendit a >> go r--{-# NOINLINE forkSVarPar #-}-forkSVarPar :: (IsStream t, MonadAsync m)- => SVarStopStyle -> t m a -> t m a -> t m a-forkSVarPar ss m r = mkStream $ \st yld sng stp -> do- sv <- newParallelVar ss st- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)- case ss of- StopBy -> liftIO $ do- set <- readIORef (workerThreads sv)- writeIORef (svarStopBy sv) $ Set.elemAt 0 set- _ -> return ()- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)- foldStream st yld sng stp (fromSVar sv)--{-# INLINE joinStreamVarPar #-}-joinStreamVarPar :: (IsStream t, MonadAsync m)- => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a-joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->- case streamVar st of- Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do- pushWorkerPar sv (runOne st $ toStream m1)- foldStreamShared st yld sng stp m2- _ -> foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)---- | XXX we can implement it more efficienty by directly implementing instead--- of combining streams using parallel.-{-# INLINE consMParallel #-}-{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}-consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a-consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)---- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'--- Merges two streams concurrently.------ @since 0.2.0-{-# INLINE parallel #-}-parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-parallel = joinStreamVarPar ParallelVar StopNone---- This is a co-parallel like combinator for streams, where first stream is the--- main stream and the rest are just supporting it, when the first ends--- everything ends.------ | Like `parallel` but stops the output as soon as the first stream stops.------ @since 0.7.0-{-# INLINE parallelFst #-}-parallelFst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-parallelFst = joinStreamVarPar ParallelVar StopBy---- This is a race like combinator for streams.------ | Like `parallel` but stops the output as soon as any of the two streams--- stops.------ @since 0.7.0-{-# INLINE parallelMin #-}-parallelMin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-parallelMin = joinStreamVarPar ParallelVar StopAny----------------------------------------------------------------------------------- Convert a stream to parallel---------------------------------------------------------------------------------mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)-mkParallel m = do- sv <- newParallelVar StopNone defState- pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)- return $ fromSVar sv----------------------------------------------------------------------------------- Stream to stream concurrent function application---------------------------------------------------------------------------------{-# INLINE applyParallel #-}-applyParallel :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b-applyParallel f m = mkStream $ \st yld sng stp -> do- sv <- newParallelVar StopNone (adaptState st)- pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))- foldStream st yld sng stp $ f $ fromSVar sv----------------------------------------------------------------------------------- Stream runner concurrent function application---------------------------------------------------------------------------------{-# INLINE foldParallel #-}-foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b-foldParallel f m = do- sv <- newParallelVar StopNone defState- pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)- f $ fromSVar sv--{-# INLINE teeToSVar #-}-teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a-teeToSVar svr m = mkStream $ \st yld sng stp -> do- foldStreamShared st yld sng stp (go svr m)-- where-- go sv m0 = mkStream $ \st yld sng stp -> do- let stop = do- liftIO $ do- incrementBufferLimit sv- sendStop sv Nothing- -- XXX wait for Stop response event on exception channel,- -- drain the exception channel.- stp- sendit a = liftIO $ do- -- XXX check for exceptions before decrement so that we do not- -- block forever if the child already exited with an exception.- decrementBufferLimit sv- void $ send sv (ChildYield a)- single a = sendit a >> (liftIO $ sendStop sv Nothing) >> sng a- yieldk a r = sendit a >> yld a (go sv r)- in foldStreamShared st yieldk single stop m0---- In case of folds the roles of worker and parent on an SVar are reversed. The--- parent stream pushes values to an SVar instead of pulling from it and a--- worker thread running the fold pulls from the SVar and folds the stream. We--- keep a separate channel for pushing exceptions in the reverse direction i.e.--- from the fold to the parent stream.------ NOTE: If we use fromSVar here it will kill the main computation (the parent)--- when the SVar goes away so we use fromStreamVar instead.--{-# NOINLINE handleChildException #-}-handleChildException :: SVar t m a -> SomeException -> IO ()-handleChildException _sv e = do- -- tid <- myThreadId- -- void $ sendReverse sv (ChildStop tid (Just e))- throwIO e---- | Redirect a copy of the stream to a supplied fold and run it concurrently--- in an independent thread. The fold may buffer some elements. The buffer size--- is determined by the prevailing 'maxBuffer' setting.------ @--- Stream m a -> m b--- |--- -----stream m a ---------------stream m a----------- @------ @--- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)--- 1--- 2--- @------ Exceptions from the concurrently running fold are propagated to the current--- computation. Note that, because of buffering in the fold, exceptions may be--- delayed and may not correspond to the current element being processed in the--- parent stream, but we guarantee that the tap finishes and all exceptions--- from it are drained before the parent stream stops.--------- Compare with 'tap'.------ @since 0.7.0-{-# INLINE tapAsync #-}-tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a-tapAsync f m = mkStream $ \st yld sng stp -> do- -- Buffer size for the SVar is derived from the current state- sv <- newParallelVar StopNone (adaptState st)- -- XXX exception handling- -- XXX if we terminate due to an exception, do we need to actively- -- terminate the fold?- liftIO myThreadId >>= modifyThread sv- void $ doFork (void $ f $ fromStream $ fromStreamVar sv)- (svarMrun sv) (handleChildException sv)- foldStreamShared st yld sng stp (teeToSVar sv m)----------------------------------------------------------------------------------- Concurrent Application---------------------------------------------------------------------------------infixr 0 |$-infixr 0 |$.--infixl 1 |&-infixl 1 |&.---- | Parallel function application operator for streams; just like the regular--- function application operator '$' except that it is concurrent. The--- following code prints a value every second even though each stage adds a 1--- second delay.--------- @--- drain $--- S.mapM (\\x -> threadDelay 1000000 >> print x)--- |$ S.repeatM (threadDelay 1000000 >> return 1)--- @------ /Concurrent/------ @since 0.3.0-{-# INLINE (|$) #-}-(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b-f |$ x = applyParallel f x---- | Parallel reverse function application operator for streams; just like the--- regular reverse function application operator '&' except that it is--- concurrent.------ @--- drain $--- S.repeatM (threadDelay 1000000 >> return 1)--- |& S.mapM (\\x -> threadDelay 1000000 >> print x)--- @------ /Concurrent/------ @since 0.3.0-{-# INLINE (|&) #-}-(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b-x |& f = f |$ x---- | Parallel function application operator; applies a @run@ or @fold@ function--- to a stream such that the fold consumer and the stream producer run in--- parallel. A @run@ or @fold@ function reduces the stream to a value in the--- underlying monad. The @.@ at the end of the operator is a mnemonic for--- termination of the stream.------ @--- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()--- |$. S.repeatM (threadDelay 1000000 >> return 1)--- @------ /Concurrent/------ @since 0.3.0-{-# INLINE (|$.) #-}-(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b-f |$. x = foldParallel f x---- | Parallel reverse function application operator for applying a run or fold--- functions to a stream. Just like '|$.' except that the operands are reversed.------ @--- S.repeatM (threadDelay 1000000 >> return 1)--- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()--- @------ /Concurrent/------ @since 0.3.0-{-# INLINE (|&.) #-}-(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b-x |&. f = f |$. x----------------------------------------------------------------------------------- ParallelT----------------------------------------------------------------------------------- | Async composition with strict concurrent execution of all streams.------ The 'Semigroup' instance of 'ParallelT' executes both the streams--- concurrently without any delay or without waiting for the consumer demand--- and /merges/ the results as they arrive. If the consumer does not consume--- the results, they are buffered upto a configured maximum, controlled by the--- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will--- block until there is space in the buffer.------ Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a--- round robin fashion. The key difference is that 'WAsyncT' might wait for the--- consumer demand before it executes the tasks whereas 'ParallelT' starts--- executing all the tasks immediately without waiting for the consumer demand.--- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does--- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is--- strict.------ 'ParallelT' is useful for cases when the streams are required to be--- evaluated simultaneously irrespective of how the consumer consumes them e.g.--- when we want to race two tasks and want to start both strictly at the same--- time or if we have timers in the parallel tasks and our results depend on--- the timers being started at the same time. If we do not have such--- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more--- efficient than 'ParallelT'.------ @--- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print--- @--- @--- [1,3,2,4]--- @------ When streams with more than one element are merged, it yields whichever--- stream yields first without any bias, unlike the 'Async' style streams.------ Any exceptions generated by a constituent stream are propagated to the--- output stream. The output and exceptions from a single stream are guaranteed--- to arrive in the same order in the resulting stream as they were generated--- in the input stream. However, the relative ordering of elements from--- different streams in the resulting stream can vary depending on scheduling--- and generation delays.------ Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations--- of the loop concurrently.------ @--- import "Streamly"--- import qualified "Streamly.Prelude" as S--- import Control.Concurrent------ main = 'drain' . 'parallely' $ do--- n <- return 3 \<\> return 2 \<\> return 1--- S.yieldM $ do--- threadDelay (n * 1000000)--- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)--- @--- @--- ThreadId 40: Delay 1--- ThreadId 39: Delay 2--- ThreadId 38: Delay 3--- @------ Note that parallel composition can only combine a finite number of--- streams as it needs to retain state for each unfinished stream.------ /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/------ /Since: 0.1.0/-newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}- deriving (MonadTrans)---- | A parallely composing IO stream of elements of type @a@.--- See 'ParallelT' documentation for more details.------ @since 0.2.0-type Parallel = ParallelT IO---- | Fix the type of a polymorphic stream as 'ParallelT'.------ @since 0.1.0-parallely :: IsStream t => ParallelT m a -> t m a-parallely = adapt--instance IsStream ParallelT where- toStream = getParallelT- fromStream = ParallelT-- {-# INLINE consM #-}- {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}- consM = consMParallel-- {-# INLINE (|:) #-}- {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}- (|:) = consM----------------------------------------------------------------------------------- Semigroup---------------------------------------------------------------------------------{-# INLINE mappendParallel #-}-{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}-mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a-mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)--instance MonadAsync m => Semigroup (ParallelT m a) where- (<>) = mappendParallel----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance MonadAsync m => Monoid (ParallelT m a) where- mempty = K.nil- mappend = (<>)----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------{-# INLINE bindParallel #-}-{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}-bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b-bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)--instance MonadAsync m => Monad (ParallelT m) where- return = pure- (>>=) = bindParallel----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)-MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
− src/Streamly/Streams/Prelude.hs
@@ -1,327 +0,0 @@-{-# LANGUAGE CPP #-}--#if __GLASGOW_HASKELL__ >= 800-{-# OPTIONS_GHC -Wno-orphans #-}-#endif--#include "inline.hs"---- |--- Module : Streamly.Streams.Prelude--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Prelude- (- -- * Stream Conversion- fromStreamS- , toStreamS-- -- * Running Effects- , drain-- -- * Conversion operations- , fromList- , toList-- -- * Fold operations- , foldrM- , foldrMx- , foldr-- , foldlx'- , foldlMx'- , foldl'- , runFold-- -- Lazy left folds are useful only for reversing the stream- , foldlS- , foldlT-- , scanlx'- , scanlMx'- , postscanlx'- , postscanlMx'-- -- * Zip style operations- , eqBy- , cmpBy-- -- * Nesting- , K.concatMapBy- , K.concatMap-- -- * Fold Utilities- , foldWith- , foldMapWith- , forEachWith- )-where--import Control.Monad.Trans (MonadTrans(..))-import Prelude hiding (foldr)-import qualified Prelude--import Streamly.Internal.Data.Fold.Types (Fold (..))--#ifdef USE_STREAMK_ONLY-import qualified Streamly.Streams.StreamK as S-#else-import qualified Streamly.Streams.StreamD as S-#endif--import Streamly.Streams.StreamK (IsStream(..))-import qualified Streamly.Streams.StreamK as K-import qualified Streamly.Streams.StreamD as D----------------------------------------------------------------------------------- Conversion to and from direct style stream----------------------------------------------------------------------------------- These definitions are dependent on what is imported as S-{-# INLINE fromStreamS #-}-fromStreamS :: (IsStream t, Monad m) => S.Stream m a -> t m a-fromStreamS = fromStream . S.toStreamK--{-# INLINE toStreamS #-}-toStreamS :: (IsStream t, Monad m) => t m a -> S.Stream m a-toStreamS = S.fromStreamK . toStream----------------------------------------------------------------------------------- Conversions---------------------------------------------------------------------------------{-# INLINE_EARLY drain #-}-drain :: (IsStream t, Monad m) => t m a -> m ()-drain m = D.drain $ D.fromStreamK (toStream m)-{-# RULES "drain fallback to CPS" [1]- forall a. D.drain (D.fromStreamK a) = K.drain a #-}----------------------------------------------------------------------------------- Conversions----------------------------------------------------------------------------------- |--- @--- fromList = 'Prelude.foldr' 'K.cons' 'K.nil'--- @------ Construct a stream from a list of pure values. This is more efficient than--- 'K.fromFoldable' for serial streams.------ @since 0.4.0-{-# INLINE_EARLY fromList #-}-fromList :: (Monad m, IsStream t) => [a] -> t m a-fromList = fromStreamS . S.fromList-{-# RULES "fromList fallback to StreamK" [1]- forall a. S.toStreamK (S.fromList a) = K.fromFoldable a #-}---- | Convert a stream into a list in the underlying monad.------ @since 0.1.0-{-# INLINE toList #-}-toList :: (Monad m, IsStream t) => t m a -> m [a]-toList m = S.toList $ toStreamS m----------------------------------------------------------------------------------- Folds---------------------------------------------------------------------------------{-# INLINE foldrM #-}-foldrM :: (Monad m, IsStream t) => (a -> m b -> m b) -> m b -> t m a -> m b-foldrM step acc m = S.foldrM step acc $ toStreamS m--{-# INLINE foldrMx #-}-foldrMx :: (Monad m, IsStream t)- => (a -> m x -> m x) -> m x -> (m x -> m b) -> t m a -> m b-foldrMx step final project m = D.foldrMx step final project $ D.toStreamD m--{-# INLINE foldr #-}-foldr :: (Monad m, IsStream t) => (a -> b -> b) -> b -> t m a -> m b-foldr f z = foldrM (\a b -> b >>= return . f a) (return z)---- | Like 'foldlx'', but with a monadic step function.------ @since 0.7.0-{-# INLINE foldlMx' #-}-foldlMx' :: (IsStream t, Monad m)- => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> m b-foldlMx' step begin done m = S.foldlMx' step begin done $ toStreamS m---- | Strict left fold with an extraction function. Like the standard strict--- left fold, but applies a user supplied extraction function (the third--- argument) to the folded value at the end. This is designed to work with the--- @foldl@ library. The suffix @x@ is a mnemonic for extraction.------ @since 0.7.0-{-# INLINE foldlx' #-}-foldlx' :: (IsStream t, Monad m)- => (x -> a -> x) -> x -> (x -> b) -> t m a -> m b-foldlx' step begin done m = S.foldlx' step begin done $ toStreamS m---- | Strict left associative fold.------ @since 0.2.0-{-# INLINE foldl' #-}-foldl' :: (Monad m, IsStream t) => (b -> a -> b) -> b -> t m a -> m b-foldl' step begin m = S.foldl' step begin $ toStreamS m--{-# INLINE foldlS #-}-foldlS :: IsStream t => (t m b -> a -> t m b) -> t m b -> t m a -> t m b-foldlS = K.foldlS---- | Lazy left fold to a transformer monad.------ For example, to reverse a stream:------ > S.toList $ S.foldlT (flip S.cons) S.nil $ (S.fromList [1..5] :: SerialT IO Int)----{-# INLINE foldlT #-}-foldlT :: (Monad m, IsStream t, Monad (s m), MonadTrans s)- => (s m b -> a -> s m b) -> s m b -> t m a -> s m b-foldlT f z s = S.foldlT f z (toStreamS s)--{-# INLINE runFold #-}-runFold :: (Monad m, IsStream t) => Fold m a b -> t m a -> m b-runFold (Fold step begin done) = foldlMx' step begin done----------------------------------------------------------------------------------- Scans----------------------------------------------------------------------------------- postscanlM' followed by mapM-{-# INLINE postscanlMx' #-}-postscanlMx' :: (IsStream t, Monad m)- => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> t m b-postscanlMx' step begin done m =- D.fromStreamD $ D.postscanlMx' step begin done $ D.toStreamD m---- postscanl' followed by map-{-# INLINE postscanlx' #-}-postscanlx' :: (IsStream t, Monad m)- => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b-postscanlx' step begin done m =- D.fromStreamD $ D.postscanlx' step begin done $ D.toStreamD m---- scanlM' followed by mapM----{-# INLINE scanlMx' #-}-scanlMx' :: (IsStream t, Monad m)- => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> t m b-scanlMx' step begin done m =- D.fromStreamD $ D.scanlMx' step begin done $ D.toStreamD m---- scanl followed by map------ | Strict left scan with an extraction function. Like 'scanl'', but applies a--- user supplied extraction function (the third argument) at each step. This is--- designed to work with the @foldl@ library. The suffix @x@ is a mnemonic for--- extraction.------ @since 0.7.0-{-# INLINE scanlx' #-}-scanlx' :: (IsStream t, Monad m)- => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b-scanlx' step begin done m =- fromStreamS $ S.scanlx' step begin done $ toStreamS m----------------------------------------------------------------------------------- Comparison----------------------------------------------------------------------------------- | Compare two streams for equality------ @since 0.5.3-{-# INLINE eqBy #-}-eqBy :: (IsStream t, Monad m) => (a -> b -> Bool) -> t m a -> t m b -> m Bool-eqBy f m1 m2 = D.eqBy f (D.toStreamD m1) (D.toStreamD m2)---- | Compare two streams------ @since 0.5.3-{-# INLINE cmpBy #-}-cmpBy- :: (IsStream t, Monad m)- => (a -> b -> Ordering) -> t m a -> t m b -> m Ordering-cmpBy f m1 m2 = D.cmpBy f (D.toStreamD m1) (D.toStreamD m2)----------------------------------------------------------------------------------- Fold Utilities---------------------------------------------------------------------------------{---- XXX do we have facilities in Foldable to fold any Foldable in this manner?------ | Perform a pair wise bottom up hierarchical fold of elements in the--- container using the given function as the merge function.------ This will perform a balanced merge sort if the merge function is--- 'mergeBy compare'.------ @since 0.7.0-{-# INLINABLE foldbWith #-}-foldbWith :: IsStream t- => (t m a -> t m a -> t m a) -> SerialT Identity (t m a) -> t m a-foldbWith f = K.foldb f K.nil--}---- /Since: 0.7.0 ("Streamly.Prelude")/------ | A variant of 'Data.Foldable.fold' that allows you to fold a 'Foldable'--- container of streams using the specified stream sum operation.------ @foldWith 'async' $ map return [1..3]@------ Equivalent to:------ @--- foldWith f = S.foldMapWith f id--- @------ /Since: 0.1.0 ("Streamly")/-{-# INLINABLE foldWith #-}-foldWith :: (IsStream t, Foldable f)- => (t m a -> t m a -> t m a) -> f (t m a) -> t m a-foldWith f = Prelude.foldr f K.nil---- /Since: 0.7.0 ("Streamly.Prelude")/------ | A variant of 'foldMap' that allows you to map a monadic streaming action--- on a 'Foldable' container and then fold it using the specified stream merge--- operation.------ @foldMapWith 'async' return [1..3]@------ Equivalent to:------ @--- foldMapWith f g xs = S.concatMapWith f g (S.fromFoldable xs)--- @------ /Since: 0.1.0 ("Streamly")/-{-# INLINABLE foldMapWith #-}-foldMapWith :: (IsStream t, Foldable f)- => (t m b -> t m b -> t m b) -> (a -> t m b) -> f a -> t m b-foldMapWith f g = Prelude.foldr (f . g) K.nil---- /Since: 0.7.0 ("Streamly.Prelude")/------ | Like 'foldMapWith' but with the last two arguments reversed i.e. the--- monadic streaming function is the last argument.------ Equivalent to:------ @--- forEachWith = flip S.foldMapWith--- @------ /Since: 0.1.0 ("Streamly")/-{-# INLINABLE forEachWith #-}-forEachWith :: (IsStream t, Foldable f)- => (t m b -> t m b -> t m b) -> f a -> (a -> t m b) -> t m b-forEachWith f xs g = Prelude.foldr (f . g) K.nil xs
− src/Streamly/Streams/SVar.hs
@@ -1,119 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}---- |--- Module : Streamly.Streams.SVar--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.SVar- ( fromSVar- , fromStreamVar- , toSVar- )-where--import Control.Exception (fromException)-import Control.Monad (when)-import Control.Monad.Catch (throwM)-import Control.Monad.IO.Class (MonadIO(liftIO))-import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)-import Data.Maybe (isNothing)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup ((<>))-#endif-import System.IO (hPutStrLn, stderr)-import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)-import System.Mem (performMajorGC)--import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK hiding (reverse)--printSVar :: SVar t m a -> String -> IO ()-printSVar sv how = do- svInfo <- dumpSVar sv- hPutStrLn stderr $ "\n" <> how <> "\n" <> svInfo---- | Pull a stream from an SVar.-{-# NOINLINE fromStreamVar #-}-fromStreamVar :: MonadAsync m => SVar Stream m a -> Stream m a-fromStreamVar sv = mkStream $ \st yld sng stp -> do- list <- readOutputQ sv- -- Reversing the output is important to guarantee that we process the- -- outputs in the same order as they were generated by the constituent- -- streams.- foldStream st yld sng stp $ processEvents $ reverse list-- where-- allDone stp = do- when (svarInspectMode sv) $ do- t <- liftIO $ getTime Monotonic- liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)- liftIO $ printSVar sv "SVar Done"- stp-- {-# INLINE processEvents #-}- processEvents [] = mkStream $ \st yld sng stp -> do- done <- postProcess sv- if done- then allDone stp- else foldStream st yld sng stp $ fromStreamVar sv-- processEvents (ev : es) = mkStream $ \st yld sng stp -> do- let rest = processEvents es- case ev of- ChildYield a -> yld a rest- ChildStop tid e -> do- accountThread sv tid- case e of- Nothing -> do- stop <- shouldStop tid- if stop- then liftIO (cleanupSVar sv) >> allDone stp- else foldStream st yld sng stp rest- Just ex ->- case fromException ex of- Just ThreadAbort ->- foldStream st yld sng stp rest- Nothing -> liftIO (cleanupSVar sv) >> throwM ex- shouldStop tid =- case svarStopStyle sv of- StopNone -> return False- StopAny -> return True- StopBy -> do- sid <- liftIO $ readIORef (svarStopBy sv)- return $ if tid == sid then True else False--{-# INLINE fromSVar #-}-fromSVar :: (MonadAsync m, IsStream t) => SVar Stream m a -> t m a-fromSVar sv =- mkStream $ \st yld sng stp -> do- ref <- liftIO $ newIORef ()- _ <- liftIO $ mkWeakIORef ref hook- -- We pass a copy of sv to fromStreamVar, so that we know that it has- -- no other references, when that copy gets garbage collected "ref"- -- will get garbage collected and our hook will be called.- foldStreamShared st yld sng stp $- fromStream $ fromStreamVar sv{svarRef = Just ref}- where-- hook = do- when (svarInspectMode sv) $ do- r <- liftIO $ readIORef (svarStopTime (svarStats sv))- when (isNothing r) $- printSVar sv "SVar Garbage Collected"- cleanupSVar sv- -- If there are any SVars referenced by this SVar a GC will prompt- -- them to be cleaned up quickly.- when (svarInspectMode sv) performMajorGC---- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then--- be read back from the SVar using 'fromSVar'.-toSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> m ()-toSVar sv m = toStreamVar sv (toStream m)
− src/Streamly/Streams/Serial.hs
@@ -1,399 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving#-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX---- |--- Module : Streamly.Streams.Serial--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Serial- (- -- * Serial appending stream- SerialT- , StreamT -- deprecated- , Serial- , K.serial- , serially-- -- * Serial interleaving stream- , WSerialT- , InterleavedT -- deprecated- , WSerial- , wSerial- , wSerialFst- , wSerialMin- , (<=>) -- deprecated- , wSerially- , interleaving -- deprecated-- -- * Transformation- , map- , mapM- )-where--import Control.Applicative (liftA2)-import Control.DeepSeq (NFData(..))-#if MIN_VERSION_deepseq(1,4,3)-import Control.DeepSeq (NFData1(..))-#endif-import Control.Monad (ap)-import Control.Monad.Base (MonadBase(..), liftBaseDefault)-import Control.Monad.Catch (MonadThrow, throwM)--- import Control.Monad.Error.Class (MonadError(..))-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader.Class (MonadReader(..))-import Control.Monad.State.Class (MonadState(..))-import Control.Monad.Trans.Class (MonadTrans(lift))-import Data.Functor.Identity (Identity(..), runIdentity)-import Data.Foldable (fold)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif-import GHC.Exts (IsList(..), IsString(..))-import Text.Read (Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec,- readListPrecDefault)-import Prelude hiding (map, mapM)--import Streamly.Streams.StreamK (IsStream(..), adapt, Stream, mkStream,- foldStream)-import qualified Streamly.Streams.Prelude as P-import qualified Streamly.Streams.StreamK as K-import qualified Streamly.Streams.StreamD as D--#include "Instances.hs"-#include "inline.hs"----------------------------------------------------------------------------------- SerialT----------------------------------------------------------------------------------- | The 'Semigroup' operation for 'SerialT' behaves like a regular append--- operation. Therefore, when @a <> b@ is evaluated, stream @a@ is evaluated--- first until it exhausts and then stream @b@ is evaluated. In other words,--- the elements of stream @b@ are appended to the elements of stream @a@. This--- operation can be used to fold an infinite lazy container of streams.------ @--- import Streamly--- import qualified "Streamly.Prelude" as S------ main = (S.toList . 'serially' $ (S.fromList [1,2]) \<\> (S.fromList [3,4])) >>= print--- @--- @--- [1,2,3,4]--- @------ The 'Monad' instance runs the /monadic continuation/ for each--- element of the stream, serially.------ @--- main = S.drain . 'serially' $ do--- x <- return 1 \<\> return 2--- S.yieldM $ print x--- @--- @--- 1--- 2--- @------ 'SerialT' nests streams serially in a depth first manner.------ @--- main = S.drain . 'serially' $ do--- x <- return 1 \<\> return 2--- y <- return 3 \<\> return 4--- S.yieldM $ print (x, y)--- @--- @--- (1,3)--- (1,4)--- (2,3)--- (2,4)--- @------ We call the monadic code being run for each element of the stream a monadic--- continuation. In imperative paradigm we can think of this composition as--- nested @for@ loops and the monadic continuation is the body of the loop. The--- loop iterates for all elements of the stream.------ Note that the behavior and semantics of 'SerialT', including 'Semigroup'--- and 'Monad' instances are exactly like Haskell lists except that 'SerialT'--- can contain effectful actions while lists are pure.------ In the code above, the 'serially' combinator can be omitted as the default--- stream type is 'SerialT'.------ @since 0.2.0-newtype SerialT m a = SerialT {getSerialT :: Stream m a}- deriving (Semigroup, Monoid, MonadTrans)---- | A serial IO stream of elements of type @a@. See 'SerialT' documentation--- for more details.------ @since 0.2.0-type Serial = SerialT IO---- |--- @since 0.1.0-{-# DEPRECATED StreamT "Please use 'SerialT' instead." #-}-type StreamT = SerialT---- | Fix the type of a polymorphic stream as 'SerialT'.------ @since 0.1.0-serially :: IsStream t => SerialT m a -> t m a-serially = adapt--{-# INLINE consMSerial #-}-{-# SPECIALIZE consMSerial :: IO a -> SerialT IO a -> SerialT IO a #-}-consMSerial :: Monad m => m a -> SerialT m a -> SerialT m a-consMSerial m ms = fromStream $ K.consMStream m (toStream ms)--instance IsStream SerialT where- toStream = getSerialT- fromStream = SerialT- consM = consMSerial- (|:) = consMSerial----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------instance Monad m => Monad (SerialT m) where- return = pure- {-# INLINE (>>=) #-}- (>>=) = K.bindWith K.serial-- -- StreamD based implementation- -- return = SerialT . D.fromStreamD . D.yield- -- m >>= f = D.fromStreamD $ D.concatMap (\a -> D.toStreamD (f a)) (D.toStreamD m)----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------{-# INLINE_EARLY mapM #-}-mapM :: (IsStream t, Monad m) => (a -> m b) -> t m a -> t m b-mapM f m = fromStream $ D.toStreamK $ D.mapM f $ D.fromStreamK (toStream m)---- |--- @--- map = fmap--- @------ Same as 'fmap'.------ @--- > S.toList $ S.map (+1) $ S.fromList [1,2,3]--- [2,3,4]--- @------ @since 0.4.0-{-# INLINE map #-}-map :: (IsStream t, Monad m) => (a -> b) -> t m a -> t m b-map f = mapM (return . f)--MONAD_APPLICATIVE_INSTANCE(SerialT,)-MONAD_COMMON_INSTANCES(SerialT,)-LIST_INSTANCES(SerialT)-NFDATA1_INSTANCE(SerialT)-FOLDABLE_INSTANCE(SerialT)-TRAVERSABLE_INSTANCE(SerialT)----------------------------------------------------------------------------------- WSerialT----------------------------------------------------------------------------------- | The 'Semigroup' operation for 'WSerialT' interleaves the elements from the--- two streams. Therefore, when @a <> b@ is evaluated, stream @a@ is evaluated--- first to produce the first element of the combined stream and then stream--- @b@ is evaluated to produce the next element of the combined stream, and--- then we go back to evaluating stream @a@ and so on. In other words, the--- elements of stream @a@ are interleaved with the elements of stream @b@.------ Note that when multiple actions are combined like @a <> b <> c ... <> z@ we--- interleave them in a binary fashion i.e. @a@ and @b@ are interleaved with--- each other and the result is interleaved with @c@ and so on. This will not--- act as a true round-robin scheduling across all the streams. Note that this--- operation cannot be used to fold a container of infinite streams as the--- state that it needs to maintain is proportional to the number of streams.------ @--- import Streamly--- import qualified "Streamly.Prelude" as S------ main = (S.toList . 'wSerially' $ (S.fromList [1,2]) \<\> (S.fromList [3,4])) >>= print--- @--- @--- [1,3,2,4]--- @------ Similarly, the 'Monad' instance interleaves the iterations of the--- inner and the outer loop, nesting loops in a breadth first manner.--------- @--- main = S.drain . 'wSerially' $ do--- x <- return 1 \<\> return 2--- y <- return 3 \<\> return 4--- S.yieldM $ print (x, y)--- @--- @--- (1,3)--- (2,3)--- (1,4)--- (2,4)--- @------ @since 0.2.0-newtype WSerialT m a = WSerialT {getWSerialT :: Stream m a}- deriving (MonadTrans)---- | An interleaving serial IO stream of elements of type @a@. See 'WSerialT'--- documentation for more details.------ @since 0.2.0-type WSerial = WSerialT IO---- |--- @since 0.1.0-{-# DEPRECATED InterleavedT "Please use 'WSerialT' instead." #-}-type InterleavedT = WSerialT---- | Fix the type of a polymorphic stream as 'WSerialT'.------ @since 0.2.0-wSerially :: IsStream t => WSerialT m a -> t m a-wSerially = adapt---- | Same as 'wSerially'.------ @since 0.1.0-{-# DEPRECATED interleaving "Please use wSerially instead." #-}-interleaving :: IsStream t => WSerialT m a -> t m a-interleaving = wSerially--consMWSerial :: Monad m => m a -> WSerialT m a -> WSerialT m a-consMWSerial m ms = fromStream $ K.consMStream m (toStream ms)--instance IsStream WSerialT where- toStream = getWSerialT- fromStream = WSerialT-- {-# INLINE consM #-}- {-# SPECIALIZE consM :: IO a -> WSerialT IO a -> WSerialT IO a #-}- consM :: Monad m => m a -> WSerialT m a -> WSerialT m a- consM = consMWSerial-- {-# INLINE (|:) #-}- {-# SPECIALIZE (|:) :: IO a -> WSerialT IO a -> WSerialT IO a #-}- (|:) :: Monad m => m a -> WSerialT m a -> WSerialT m a- (|:) = consMWSerial----------------------------------------------------------------------------------- Semigroup----------------------------------------------------------------------------------- Additionally we can have m elements yield from the first stream and n--- elements yeilding from the second stream. We can also have time slicing--- variants of positional interleaving, e.g. run first stream for m seconds and--- run the second stream for n seconds.------ Similar combinators can be implemented using WAhead style.---- | Polymorphic version of the 'Semigroup' operation '<>' of 'WSerialT'.--- Interleaves two streams, yielding one element from each stream alternately.--- When one stream stops the rest of the other stream is used in the output--- stream.------ @since 0.2.0-{-# INLINE wSerial #-}-wSerial :: IsStream t => t m a -> t m a -> t m a-wSerial m1 m2 = mkStream $ \st yld sng stp -> do- let stop = foldStream st yld sng stp m2- single a = yld a m2- yieldk a r = yld a (wSerial m2 r)- foldStream st yieldk single stop m1---- | Like `wSerial` but stops interleaving as soon as the first stream stops.------ @since 0.7.0-{-# INLINE wSerialFst #-}-wSerialFst :: IsStream t => t m a -> t m a -> t m a-wSerialFst m1 m2 = mkStream $ \st yld sng stp -> do- let yieldFirst a r = yld a (yieldSecond r m2)- in foldStream st yieldFirst sng stp m1-- where-- yieldSecond s1 s2 = mkStream $ \st yld sng stp -> do- let stop = foldStream st yld sng stp s1- single a = yld a s1- yieldk a r = yld a (wSerial s1 r)- in foldStream st yieldk single stop s2---- | Like `wSerial` but stops interleaving as soon as any of the two streams--- stops.------ @since 0.7.0-{-# INLINE wSerialMin #-}-wSerialMin :: IsStream t => t m a -> t m a -> t m a-wSerialMin m1 m2 = mkStream $ \st yld sng stp -> do- let stop = stp- single a = sng a- yieldk a r = yld a (wSerial m2 r)- foldStream st yieldk single stop m1--instance Semigroup (WSerialT m a) where- (<>) = wSerial--infixr 5 <=>---- | Same as 'wSerial'.------ @since 0.1.0-{-# DEPRECATED (<=>) "Please use 'wSerial' instead." #-}-{-# INLINE (<=>) #-}-(<=>) :: IsStream t => t m a -> t m a -> t m a-(<=>) = wSerial----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance Monoid (WSerialT m a) where- mempty = K.nil- mappend = (<>)----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------instance Monad m => Monad (WSerialT m) where- return = pure- {-# INLINE (>>=) #-}- (>>=) = K.bindWith wSerial----------------------------------------------------------------------------------- Other instances---------------------------------------------------------------------------------MONAD_APPLICATIVE_INSTANCE(WSerialT,)-MONAD_COMMON_INSTANCES(WSerialT,)-LIST_INSTANCES(WSerialT)-NFDATA1_INSTANCE(WSerialT)-FOLDABLE_INSTANCE(WSerialT)-TRAVERSABLE_INSTANCE(WSerialT)
− src/Streamly/Streams/StreamD.hs
@@ -1,3950 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE MagicHash #-}--#if __GLASGOW_HASKELL__ >= 801-{-# LANGUAGE TypeApplications #-}-#endif--#include "inline.hs"---- |--- Module : Streamly.Streams.StreamD--- Copyright : (c) 2018 Harendra Kumar--- Copyright : (c) Roman Leshchinskiy 2008-2010--- Copyright : (c) The University of Glasgow, 2009--- Copyright : (c) Bjoern Hoehrmann 2008-2009------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Direct style re-implementation of CPS style stream in StreamK module. The--- symbol or suffix 'D' in this module denotes the "Direct" style. GHC is able--- to INLINE and fuse direct style better, providing better performance than--- CPS implementation.------ @--- import qualified Streamly.Streams.StreamD as D--- @---- Some of the functions in this file have been adapted from the vector--- library, https://hackage.haskell.org/package/vector.--module Streamly.Streams.StreamD- (- -- * The stream type- Step (..)- , Stream (..)-- -- * Construction- , nil- , nilM- , cons-- -- * Deconstruction- , uncons-- -- * Generation- -- ** Unfolds- , unfoldr- , unfoldrM- , unfold-- -- ** Specialized Generation- -- | Generate a monadic stream from a seed.- , repeat- , repeatM- , replicate- , replicateM- , fromIndices- , fromIndicesM- , generate- , generateM-- -- ** Enumerations- , enumerateFromStepIntegral- , enumerateFromIntegral- , enumerateFromThenIntegral- , enumerateFromToIntegral- , enumerateFromThenToIntegral-- , enumerateFromStepNum- , numFrom- , numFromThen- , enumerateFromToFractional- , enumerateFromThenToFractional-- -- ** Conversions- -- | Transform an input structure into a stream.- -- | Direct style stream does not support @fromFoldable@.- , yield- , yieldM- , fromList- , fromListM- , fromStreamK- , fromStreamD-- -- * Elimination- -- ** General Folds- , foldrS- , foldrT- , foldrM- , foldrMx- , foldr- , foldr1-- , foldl'- , foldlM'- , foldlS- , foldlT- , reverse- , reverse'-- , foldlx'- , foldlMx'-- -- ** Specialized Folds- , tap- , drain- , null- , head- , tail- , last- , elem- , notElem- , all- , any- , maximum- , maximumBy- , minimum- , minimumBy- , findIndices- , lookup- , findM- , find- , (!!)-- -- ** Flattening nested streams- , concatMapM- , concatMap- , ConcatMapUState (..)- , concatMapU- , ConcatUnfoldInterleaveState (..)- , concatUnfoldInterleave- , concatUnfoldRoundrobin- , AppendState(..)- , append- , InterleaveState(..)- , interleave- , interleaveMin- , interleaveSuffix- , interleaveInfix- , roundRobin -- interleaveFair?/ParallelFair- , gintercalateSuffix- , interposeSuffix- , gintercalate- , interpose-- -- ** Grouping- , groupsOf- , groupsOf2- , groupsBy- , groupsRollingBy-- -- ** Splitting- , splitBy- , splitSuffixBy- , wordsBy- , splitSuffixBy'-- , splitOn- , splitSuffixOn-- , splitInnerBy- , splitInnerBySuffix-- -- ** Substreams- , isPrefixOf- , isSubsequenceOf- , stripPrefix-- -- ** Map and Fold- , mapM_-- -- ** Conversions- -- | Transform a stream into another type.- , toList- , toListRev- , toStreamK- , toStreamD-- , hoist- , generally-- , liftInner- , runReaderT- , evalStateT- , runStateT-- -- * Transformation- , transform-- -- ** By folding (scans)- , scanlM'- , scanl'- , scanlM- , scanl- , scanl1M'- , scanl1'- , scanl1M- , scanl1-- , prescanl'- , prescanlM'-- , postscanl- , postscanlM- , postscanl'- , postscanlM'-- , postscanlx'- , postscanlMx'- , scanlMx'- , scanlx'-- -- * Filtering- , filter- , filterM- , uniq- , take- , takeWhile- , takeWhileM- , drop- , dropWhile- , dropWhileM-- -- * Mapping- , map- , mapM- , sequence-- -- * Inserting- , intersperseM- , intersperse- , intersperseSuffix- , insertBy-- -- * Deleting- , deleteBy-- -- ** Map and Filter- , mapMaybe- , mapMaybeM-- -- * Zipping- , indexed- , indexedR- , zipWith- , zipWithM-- -- * Comparisons- , eqBy- , cmpBy-- -- * Merging- , mergeBy- , mergeByM-- -- * Transformation comprehensions- , the-- -- * Exceptions- , gbracket- , before- , after- , bracket- , onException- , finally- , handle-- -- * UTF8 Encoding / Decoding transformations.- , DecodeError(..)- , DecodeState- , CodePoint- , decodeUtf8- , encodeUtf8- , decodeUtf8Lenient- , decodeUtf8Either- , resumeDecodeUtf8Either- , decodeUtf8Arrays- , decodeUtf8ArraysLenient- )-where--import Control.Exception (Exception, SomeException)-import Control.Monad (void)-import Control.Monad.Catch (MonadCatch)-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader (ReaderT)-import Control.Monad.State.Strict (StateT)-import Control.Monad.Trans (MonadTrans(lift))-import Data.Bits (shiftR, shiftL, (.|.), (.&.))-import Data.Functor.Identity (Identity(..))-import Data.Maybe (fromJust, isJust)-import Data.Word (Word32)-import Foreign.Ptr (Ptr)-import Foreign.Storable (Storable(..))-import GHC.Base (assert, unsafeChr, ord)-import GHC.IO.Encoding.Failure (isSurrogate)-import GHC.ForeignPtr (ForeignPtr (..))-import GHC.Ptr (Ptr (..))-import GHC.Types (SPEC(..))-import GHC.Word (Word8(..))-import System.IO.Unsafe (unsafePerformIO)-import Prelude- hiding (map, mapM, mapM_, repeat, foldr, last, take, filter,- takeWhile, drop, dropWhile, all, any, maximum, minimum, elem,- notElem, null, head, tail, zipWith, lookup, foldr1, sequence,- (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,- reverse)--import qualified Control.Monad.Catch as MC-import qualified Control.Monad.Reader as Reader-import qualified Control.Monad.State.Strict as State--import Streamly.Internal.Memory.Array.Types (Array(..))-import Streamly.Internal.Data.Fold.Types (Fold(..))-import Streamly.Internal.Data.Pipe.Types (Pipe(..), PipeState(..))-import Streamly.Internal.Data.SVar (MonadAsync, defState, adaptState)-import Streamly.Internal.Data.Unfold.Types (Unfold(..))-import Streamly.Internal.Data.Strict (Tuple'(..))--import Streamly.Internal.Data.Stream.StreamD.Type--import qualified Streamly.Internal.Data.Pipe.Types as Pipe-import qualified Streamly.Internal.Memory.Array.Types as A-import qualified Streamly.Memory.Ring as RB-import qualified Streamly.Streams.StreamK as K--import Foreign.Ptr (plusPtr)-import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)-import Foreign.ForeignPtr (touchForeignPtr)----------------------------------------------------------------------------------- Construction----------------------------------------------------------------------------------- | An empty 'Stream'.-{-# INLINE_NORMAL nil #-}-nil :: Monad m => Stream m a-nil = Stream (\_ _ -> return Stop) ()---- | An empty 'Stream' with a side effect.-{-# INLINE_NORMAL nilM #-}-nilM :: Monad m => m b -> Stream m a-nilM m = Stream (\_ _ -> m >> return Stop) ()--{-# INLINE_NORMAL consM #-}-consM :: Monad m => m a -> Stream m a -> Stream m a-consM m (Stream step state) = Stream step1 Nothing- where- {-# INLINE_LATE step1 #-}- step1 _ Nothing = m >>= \x -> return $ Yield x (Just state)- step1 gst (Just st) = do- r <- step gst st- return $- case r of- Yield a s -> Yield a (Just s)- Skip s -> Skip (Just s)- Stop -> Stop---- XXX implement in terms of consM?--- cons x = consM (return x)------ | Can fuse but has O(n^2) complexity.-{-# INLINE_NORMAL cons #-}-cons :: Monad m => a -> Stream m a -> Stream m a-cons x (Stream step state) = Stream step1 Nothing- where- {-# INLINE_LATE step1 #-}- step1 _ Nothing = return $ Yield x (Just state)- step1 gst (Just st) = do- r <- step gst st- return $- case r of- Yield a s -> Yield a (Just s)- Skip s -> Skip (Just s)- Stop -> Stop------------------------------------------------------------------------------------ Deconstruction------------------------------------------------------------------------------------ Does not fuse, has the same performance as the StreamK version.-{-# INLINE_NORMAL uncons #-}-uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))-uncons (UnStream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield x s -> return $ Just (x, Stream step s)- Skip s -> go s- Stop -> return Nothing----------------------------------------------------------------------------------- Generation by unfold---------------------------------------------------------------------------------{-# INLINE_NORMAL unfoldrM #-}-unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a-unfoldrM next state = Stream step state- where- {-# INLINE_LATE step #-}- step _ st = do- r <- next st- return $ case r of- Just (x, s) -> Yield x s- Nothing -> Stop--{-# INLINE_LATE unfoldr #-}-unfoldr :: Monad m => (s -> Maybe (a, s)) -> s -> Stream m a-unfoldr f = unfoldrM (return . f)---- | Convert an 'Unfold' into a 'Stream' by supplying it a seed.----{-# INLINE_NORMAL unfold #-}-unfold :: Monad m => Unfold m a b -> a -> Stream m b-unfold (Unfold ustep inject) seed = Stream step Nothing- where- {-# INLINE_LATE step #-}- step _ Nothing = inject seed >>= return . Skip . Just- step _ (Just st) = do- r <- ustep st- return $ case r of- Yield x s -> Yield x (Just s)- Skip s -> Skip (Just s)- Stop -> Stop----------------------------------------------------------------------------------- Specialized Generation---------------------------------------------------------------------------------repeatM :: Monad m => m a -> Stream m a-repeatM x = Stream (\_ _ -> x >>= \r -> return $ Yield r ()) ()--repeat :: Monad m => a -> Stream m a-repeat x = Stream (\_ _ -> return $ Yield x ()) ()--{-# INLINE_NORMAL replicateM #-}-replicateM :: forall m a. Monad m => Int -> m a -> Stream m a-replicateM n p = Stream step n- where- {-# INLINE_LATE step #-}- step _ (i :: Int)- | i <= 0 = return Stop- | otherwise = do- x <- p- return $ Yield x (i - 1)--{-# INLINE_NORMAL replicate #-}-replicate :: Monad m => Int -> a -> Stream m a-replicate n x = replicateM n (return x)---- This would not work properly for floats, therefore we put an Integral--- constraint.--- | Can be used to enumerate unbounded integrals. This does not check for--- overflow or underflow for bounded integrals.-{-# INLINE_NORMAL enumerateFromStepIntegral #-}-enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a-enumerateFromStepIntegral from stride =- from `seq` stride `seq` Stream step from- where- {-# INLINE_LATE step #-}- step _ !x = return $ Yield x $! (x + stride)---- We are assuming that "to" is constrained by the type to be within--- max/min bounds.-{-# INLINE enumerateFromToIntegral #-}-enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a-enumerateFromToIntegral from to =- takeWhile (<= to) $ enumerateFromStepIntegral from 1--{-# INLINE enumerateFromIntegral #-}-enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a-enumerateFromIntegral from = enumerateFromToIntegral from maxBound--data EnumState a = EnumInit | EnumYield a a a | EnumStop--{-# INLINE_NORMAL enumerateFromThenToIntegralUp #-}-enumerateFromThenToIntegralUp- :: (Monad m, Integral a)- => a -> a -> a -> Stream m a-enumerateFromThenToIntegralUp from next to = Stream step EnumInit- where- {-# INLINE_LATE step #-}- step _ EnumInit =- return $- if to < next- then if to < from- then Stop- else Yield from EnumStop- else -- from <= next <= to- let stride = next - from- in Skip $ EnumYield from stride (to - stride)-- step _ (EnumYield x stride toMinus) =- return $- if x > toMinus- then Yield x EnumStop- else Yield x $ EnumYield (x + stride) stride toMinus-- step _ EnumStop = return Stop--{-# INLINE_NORMAL enumerateFromThenToIntegralDn #-}-enumerateFromThenToIntegralDn- :: (Monad m, Integral a)- => a -> a -> a -> Stream m a-enumerateFromThenToIntegralDn from next to = Stream step EnumInit- where- {-# INLINE_LATE step #-}- step _ EnumInit =- return $ if to > next- then if to > from- then Stop- else Yield from EnumStop- else -- from >= next >= to- let stride = next - from- in Skip $ EnumYield from stride (to - stride)-- step _ (EnumYield x stride toMinus) =- return $- if x < toMinus- then Yield x EnumStop- else Yield x $ EnumYield (x + stride) stride toMinus-- step _ EnumStop = return Stop--{-# INLINE_NORMAL enumerateFromThenToIntegral #-}-enumerateFromThenToIntegral- :: (Monad m, Integral a)- => a -> a -> a -> Stream m a-enumerateFromThenToIntegral from next to- | next >= from = enumerateFromThenToIntegralUp from next to- | otherwise = enumerateFromThenToIntegralDn from next to--{-# INLINE_NORMAL enumerateFromThenIntegral #-}-enumerateFromThenIntegral- :: (Monad m, Integral a, Bounded a)- => a -> a -> Stream m a-enumerateFromThenIntegral from next =- if next > from- then enumerateFromThenToIntegralUp from next maxBound- else enumerateFromThenToIntegralDn from next minBound---- For floating point numbers if the increment is less than the precision then--- it just gets lost. Therefore we cannot always increment it correctly by just--- repeated addition.--- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15--- 9007199254740992 + 2 :: Double => 9.007199254740994e15---- Instead we accumulate the increment counter and compute the increment--- everytime before adding it to the starting number.------ This works for Integrals as well as floating point numbers, but--- enumerateFromStepIntegral is faster for integrals.-{-# INLINE_NORMAL enumerateFromStepNum #-}-enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a-enumerateFromStepNum from stride = Stream step 0- where- {-# INLINE_LATE step #-}- step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1)--{-# INLINE_NORMAL numFrom #-}-numFrom :: (Monad m, Num a) => a -> Stream m a-numFrom from = enumerateFromStepNum from 1--{-# INLINE_NORMAL numFromThen #-}-numFromThen :: (Monad m, Num a) => a -> a -> Stream m a-numFromThen from next = enumerateFromStepNum from (next - from)---- We cannot write a general function for Num. The only way to write code--- portable between the two is to use a 'Real' constraint and convert between--- Fractional and Integral using fromRational which is horribly slow.-{-# INLINE_NORMAL enumerateFromToFractional #-}-enumerateFromToFractional- :: (Monad m, Fractional a, Ord a)- => a -> a -> Stream m a-enumerateFromToFractional from to =- takeWhile (<= to + 1 / 2) $ enumerateFromStepNum from 1--{-# INLINE_NORMAL enumerateFromThenToFractional #-}-enumerateFromThenToFractional- :: (Monad m, Fractional a, Ord a)- => a -> a -> a -> Stream m a-enumerateFromThenToFractional from next to =- takeWhile predicate $ numFromThen from next- where- mid = (next - from) / 2- predicate | next >= from = (<= to + mid)- | otherwise = (>= to + mid)------------------------------------------------------------------------------------ Generation by Conversion----------------------------------------------------------------------------------{-# INLINE_NORMAL fromIndicesM #-}-fromIndicesM :: Monad m => (Int -> m a) -> Stream m a-fromIndicesM gen = Stream step 0- where- {-# INLINE_LATE step #-}- step _ i = do- x <- gen i- return $ Yield x (i + 1)--{-# INLINE fromIndices #-}-fromIndices :: Monad m => (Int -> a) -> Stream m a-fromIndices gen = fromIndicesM (return . gen)--{-# INLINE_NORMAL generateM #-}-generateM :: Monad m => Int -> (Int -> m a) -> Stream m a-generateM n gen = n `seq` Stream step 0- where- {-# INLINE_LATE step #-}- step _ i | i < n = do- x <- gen i- return $ Yield x (i + 1)- | otherwise = return Stop--{-# INLINE generate #-}-generate :: Monad m => Int -> (Int -> a) -> Stream m a-generate n gen = generateM n (return . gen)---- XXX we need the MonadAsync constraint because of a rewrite rule.--- | Convert a list of monadic actions to a 'Stream'-{-# INLINE_LATE fromListM #-}-fromListM :: MonadAsync m => [m a] -> Stream m a-fromListM = Stream step- where- {-# INLINE_LATE step #-}- step _ (m:ms) = m >>= \x -> return $ Yield x ms- step _ [] = return Stop--{-# INLINE toStreamD #-}-toStreamD :: (K.IsStream t, Monad m) => t m a -> Stream m a-toStreamD = fromStreamK . K.toStream--{-# INLINE_NORMAL hoist #-}-hoist :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a-hoist f (Stream step state) = (Stream step' state)- where- step' gst st = do- r <- f $ step (adaptState gst) st- return $ case r of- Yield x s -> Yield x s- Skip s -> Skip s- Stop -> Stop--{-# INLINE_NORMAL generally #-}-generally :: Monad m => Stream Identity a -> Stream m a-generally = hoist (return . runIdentity)--{-# INLINE_NORMAL liftInner #-}-liftInner :: (Monad m, MonadTrans t, Monad (t m))- => Stream m a -> Stream (t m) a-liftInner (Stream step state) = Stream step' state- where- step' gst st = do- r <- lift $ step (adaptState gst) st- return $ case r of- Yield x s -> Yield x s- Skip s -> Skip s- Stop -> Stop--{-# INLINE_NORMAL runReaderT #-}-runReaderT :: Monad m => s -> Stream (ReaderT s m) a -> Stream m a-runReaderT sval (Stream step state) = Stream step' state- where- step' gst st = do- r <- Reader.runReaderT (step (adaptState gst) st) sval- return $ case r of- Yield x s -> Yield x s- Skip s -> Skip s- Stop -> Stop--{-# INLINE_NORMAL evalStateT #-}-evalStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m a-evalStateT sval (Stream step state) = Stream step' (state, sval)- where- step' gst (st, sv) = do- (r, sv') <- State.runStateT (step (adaptState gst) st) sv- return $ case r of- Yield x s -> Yield x (s, sv')- Skip s -> Skip (s, sv')- Stop -> Stop--{-# INLINE_NORMAL runStateT #-}-runStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m (s, a)-runStateT sval (Stream step state) = Stream step' (state, sval)- where- step' gst (st, sv) = do- (r, sv') <- State.runStateT (step (adaptState gst) st) sv- return $ case r of- Yield x s -> Yield (sv', x) (s, sv')- Skip s -> Skip (s, sv')- Stop -> Stop----------------------------------------------------------------------------------- Elimination by Folds------------------------------------------------------------------------------------------------------------------------------------------------------------------ Right Folds---------------------------------------------------------------------------------{-# INLINE_NORMAL foldr1 #-}-foldr1 :: Monad m => (a -> a -> a) -> Stream m a -> m (Maybe a)-foldr1 f m = do- r <- uncons m- case r of- Nothing -> return Nothing- Just (h, t) -> fmap Just (foldr f h t)----------------------------------------------------------------------------------- Left Folds---------------------------------------------------------------------------------{-# INLINE_NORMAL foldlT #-}-foldlT :: (Monad m, Monad (s m), MonadTrans s)- => (s m b -> a -> s m b) -> s m b -> Stream m a -> s m b-foldlT fstep begin (Stream step state) = go SPEC begin state- where- go !_ acc st = do- r <- lift $ step defState st- case r of- Yield x s -> go SPEC (fstep acc x) s- Skip s -> go SPEC acc s- Stop -> acc---- Note, this is going to have horrible performance, because of the nature of--- the stream type (i.e. direct stream vs CPS). Its only for reference, it is--- likely be practically unusable.-{-# INLINE_NORMAL foldlS #-}-foldlS :: Monad m- => (Stream m b -> a -> Stream m b) -> Stream m b -> Stream m a -> Stream m b-foldlS fstep begin (Stream step state) = Stream step' (Left (state, begin))- where- step' gst (Left (st, acc)) = do- r <- step (adaptState gst) st- return $ case r of- Yield x s -> Skip (Left (s, fstep acc x))- Skip s -> Skip (Left (s, acc))- Stop -> Skip (Right acc)-- step' gst (Right (Stream stp stt)) = do- r <- stp (adaptState gst) stt- return $ case r of- Yield x s -> Yield x (Right (Stream stp s))- Skip s -> Skip (Right (Stream stp s))- Stop -> Stop----------------------------------------------------------------------------------- Specialized Folds----------------------------------------------------------------------------------- | Run a streaming composition, discard the results.-{-# INLINE_LATE drain #-}-drain :: Monad m => Stream m a -> m ()--- drain = foldrM (\_ xs -> xs) (return ())-drain (Stream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield _ s -> go SPEC s- Skip s -> go SPEC s- Stop -> return ()--{-# INLINE_NORMAL null #-}-null :: Monad m => Stream m a -> m Bool-null m = foldrM (\_ _ -> return False) (return True) m--{-# INLINE_NORMAL head #-}-head :: Monad m => Stream m a -> m (Maybe a)-head m = foldrM (\x _ -> return (Just x)) (return Nothing) m---- Does not fuse, has the same performance as the StreamK version.-{-# INLINE_NORMAL tail #-}-tail :: Monad m => Stream m a -> m (Maybe (Stream m a))-tail (UnStream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield _ s -> return (Just $ Stream step s)- Skip s -> go s- Stop -> return Nothing---- XXX will it fuse? need custom impl?-{-# INLINE_NORMAL last #-}-last :: Monad m => Stream m a -> m (Maybe a)-last = foldl' (\_ y -> Just y) Nothing--{-# INLINE_NORMAL elem #-}-elem :: (Monad m, Eq a) => a -> Stream m a -> m Bool--- elem e m = foldrM (\x xs -> if x == e then return True else xs) (return False) m-elem e (Stream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield x s- | x == e -> return True- | otherwise -> go s- Skip s -> go s- Stop -> return False--{-# INLINE_NORMAL notElem #-}-notElem :: (Monad m, Eq a) => a -> Stream m a -> m Bool-notElem e s = fmap not (elem e s)--{-# INLINE_NORMAL all #-}-all :: Monad m => (a -> Bool) -> Stream m a -> m Bool--- all p m = foldrM (\x xs -> if p x then xs else return False) (return True) m-all p (Stream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield x s- | p x -> go s- | otherwise -> return False- Skip s -> go s- Stop -> return True--{-# INLINE_NORMAL any #-}-any :: Monad m => (a -> Bool) -> Stream m a -> m Bool--- any p m = foldrM (\x xs -> if p x then return True else xs) (return False) m-any p (Stream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield x s- | p x -> return True- | otherwise -> go s- Skip s -> go s- Stop -> return False--{-# INLINE_NORMAL maximum #-}-maximum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)-maximum (Stream step state) = go Nothing state- where- go Nothing st = do- r <- step defState st- case r of- Yield x s -> go (Just x) s- Skip s -> go Nothing s- Stop -> return Nothing- go (Just acc) st = do- r <- step defState st- case r of- Yield x s- | acc <= x -> go (Just x) s- | otherwise -> go (Just acc) s- Skip s -> go (Just acc) s- Stop -> return (Just acc)--{-# INLINE_NORMAL maximumBy #-}-maximumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)-maximumBy cmp (Stream step state) = go Nothing state- where- go Nothing st = do- r <- step defState st- case r of- Yield x s -> go (Just x) s- Skip s -> go Nothing s- Stop -> return Nothing- go (Just acc) st = do- r <- step defState st- case r of- Yield x s -> case cmp acc x of- GT -> go (Just acc) s- _ -> go (Just x) s- Skip s -> go (Just acc) s- Stop -> return (Just acc)--{-# INLINE_NORMAL minimum #-}-minimum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)-minimum (Stream step state) = go Nothing state- where- go Nothing st = do- r <- step defState st- case r of- Yield x s -> go (Just x) s- Skip s -> go Nothing s- Stop -> return Nothing- go (Just acc) st = do- r <- step defState st- case r of- Yield x s- | acc <= x -> go (Just acc) s- | otherwise -> go (Just x) s- Skip s -> go (Just acc) s- Stop -> return (Just acc)--{-# INLINE_NORMAL minimumBy #-}-minimumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)-minimumBy cmp (Stream step state) = go Nothing state- where- go Nothing st = do- r <- step defState st- case r of- Yield x s -> go (Just x) s- Skip s -> go Nothing s- Stop -> return Nothing- go (Just acc) st = do- r <- step defState st- case r of- Yield x s -> case cmp acc x of- GT -> go (Just x) s- _ -> go (Just acc) s- Skip s -> go (Just acc) s- Stop -> return (Just acc)--{-# INLINE_NORMAL (!!) #-}-(!!) :: (Monad m) => Stream m a -> Int -> m (Maybe a)-(Stream step state) !! i = go i state- where- go n st = do- r <- step defState st- case r of- Yield x s | n < 0 -> return Nothing- | n == 0 -> return $ Just x- | otherwise -> go (n - 1) s- Skip s -> go n s- Stop -> return Nothing--{-# INLINE_NORMAL lookup #-}-lookup :: (Monad m, Eq a) => a -> Stream m (a, b) -> m (Maybe b)-lookup e m = foldrM (\(a, b) xs -> if e == a then return (Just b) else xs)- (return Nothing) m--{-# INLINE_NORMAL findM #-}-findM :: Monad m => (a -> m Bool) -> Stream m a -> m (Maybe a)-findM p m = foldrM (\x xs -> p x >>= \r -> if r then return (Just x) else xs)- (return Nothing) m--{-# INLINE find #-}-find :: Monad m => (a -> Bool) -> Stream m a -> m (Maybe a)-find p = findM (return . p)--{-# INLINE_NORMAL findIndices #-}-findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int-findIndices p (Stream step state) = Stream step' (state, 0)- where- {-# INLINE_LATE step' #-}- step' gst (st, i) = do- r <- step (adaptState gst) st- return $ case r of- Yield x s -> if p x then Yield i (s, i+1) else Skip (s, i+1)- Skip s -> Skip (s, i+1)- Stop -> Stop--{-# INLINE toListRev #-}-toListRev :: Monad m => Stream m a -> m [a]-toListRev = foldl' (flip (:)) []---- We can implement reverse as:------ > reverse = foldlS (flip cons) nil------ However, this implementation is unusable because of the horrible performance--- of cons. So we just convert it to a list first and then stream from the--- list.------ XXX Maybe we can use an Array instead of a list here?-{-# INLINE_NORMAL reverse #-}-reverse :: Monad m => Stream m a -> Stream m a-reverse m = Stream step Nothing- where- {-# INLINE_LATE step #-}- step _ Nothing = do- xs <- toListRev m- return $ Skip (Just xs)- step _ (Just (x:xs)) = return $ Yield x (Just xs)- step _ (Just []) = return Stop---- Much faster reverse for Storables-{-# INLINE_NORMAL reverse' #-}-reverse' :: forall m a. (MonadIO m, Storable a) => Stream m a -> Stream m a-{---- This commented implementation copies the whole stream into one single array--- and then streams from that array, this is 3-4x faster than the chunked code--- that follows. Though this could be problematic due to unbounded large--- allocations. We need to figure out why the chunked code is slower and if we--- can optimize the chunked code to work as fast as this one. It may be a--- fusion issue?-import Foreign.ForeignPtr (touchForeignPtr)-import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)-import Foreign.Ptr (Ptr, plusPtr)-reverse' m = Stream step Nothing- where- {-# INLINE_LATE step #-}- step _ Nothing = do- arr <- A.fromStreamD m- let p = aEnd arr `plusPtr` negate (sizeOf (undefined :: a))- return $ Skip $ Just (aStart arr, p)-- step _ (Just (start, p)) | p < unsafeForeignPtrToPtr start = return Stop-- step _ (Just (start, p)) = do- let !x = A.unsafeInlineIO $ do- r <- peek p- touchForeignPtr start- return r- next = p `plusPtr` negate (sizeOf (undefined :: a))- return $ Yield x (Just (start, next))--}-reverse' m =- A.flattenArraysRev- $ fromStreamK- $ K.reverse- $ toStreamK- $ A.fromStreamDArraysOf A.defaultChunkSize m------------------------------------------------------------------------------------ Grouping/Splitting---------------------------------------------------------------------------------{-# INLINE_NORMAL splitSuffixBy' #-}-splitSuffixBy' :: Monad m- => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitSuffixBy' predicate f (Stream step state) =- Stream (stepOuter f) (Just state)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter (Fold fstep initial done) gst (Just st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- acc' <- fstep acc x- if (predicate x)- then done acc' >>= \val -> return $ Yield val (Just s)- else go SPEC s acc'-- Skip s -> return $ Skip $ Just s- Stop -> return Stop-- where-- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- acc' <- fstep acc x- if (predicate x)- then done acc' >>= \val -> return $ Yield val (Just s)- else go SPEC s acc'- Skip s -> go SPEC s acc- Stop -> done acc >>= \val -> return $ Yield val Nothing-- stepOuter _ _ Nothing = return Stop--{-# INLINE_NORMAL groupsBy #-}-groupsBy :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Stream m a- -> Stream m b-groupsBy cmp f (Stream step state) = Stream (stepOuter f) (Just state, Nothing)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter (Fold fstep initial done) gst (Just st, Nothing) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- acc' <- fstep acc x- go SPEC x s acc'-- Skip s -> return $ Skip $ (Just s, Nothing)- Stop -> return Stop-- where-- go !_ prev stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if cmp x prev- then do- acc' <- fstep acc x- go SPEC prev s acc'- else done acc >>= \r -> return $ Yield r (Just s, Just x)- Skip s -> go SPEC prev s acc- Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)-- stepOuter (Fold fstep initial done) gst (Just st, Just prev) = do- acc <- initial- acc' <- fstep acc prev- go SPEC st acc'-- where-- -- XXX code duplicated from the previous equation- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if cmp x prev- then do- acc' <- fstep acc x- go SPEC s acc'- else done acc >>= \r -> return $ Yield r (Just s, Just x)- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)-- stepOuter _ _ (Nothing,_) = return Stop--{-# INLINE_NORMAL groupsRollingBy #-}-groupsRollingBy :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Stream m a- -> Stream m b-groupsRollingBy cmp f (Stream step state) =- Stream (stepOuter f) (Just state, Nothing)- where-- {-# INLINE_LATE stepOuter #-}- stepOuter (Fold fstep initial done) gst (Just st, Nothing) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- acc' <- fstep acc x- go SPEC x s acc'-- Skip s -> return $ Skip $ (Just s, Nothing)- Stop -> return Stop-- where- go !_ prev stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if cmp prev x- then do- acc' <- fstep acc x- go SPEC x s acc'- else- done acc >>= \r -> return $ Yield r (Just s, Just x)- Skip s -> go SPEC prev s acc- Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)-- stepOuter (Fold fstep initial done) gst (Just st, Just prev') = do- acc <- initial- acc' <- fstep acc prev'- go SPEC prev' st acc'-- where- go !_ prevv stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if cmp prevv x- then do- acc' <- fstep acc x- go SPEC x s acc'- else done acc >>= \r -> return $ Yield r (Just s, Just x)- Skip s -> go SPEC prevv s acc- Stop -> done acc >>= \r -> return $ Yield r (Nothing, Nothing)-- stepOuter _ _ (Nothing, _) = return Stop--{-# INLINE_NORMAL splitBy #-}-splitBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitBy predicate f (Stream step state) = Stream (step' f) (Just state)-- where-- {-# INLINE_LATE step' #-}- step' (Fold fstep initial done) gst (Just st) = initial >>= go SPEC st-- where-- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if predicate x- then done acc >>= \r -> return $ Yield r (Just s)- else do- acc' <- fstep acc x- go SPEC s acc'- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r Nothing-- step' _ _ Nothing = return Stop---- XXX requires -funfolding-use-threshold=150 in lines-unlines benchmark-{-# INLINE_NORMAL splitSuffixBy #-}-splitSuffixBy :: Monad m- => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitSuffixBy predicate f (Stream step state) = Stream (step' f) (Just state)-- where-- {-# INLINE_LATE step' #-}- step' (Fold fstep initial done) gst (Just st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- if predicate x- then done acc >>= \val -> return $ Yield val (Just s)- else do- acc' <- fstep acc x- go SPEC s acc'-- Skip s -> return $ Skip $ Just s- Stop -> return Stop-- where-- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if predicate x- then done acc >>= \r -> return $ Yield r (Just s)- else do- acc' <- fstep acc x- go SPEC s acc'- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r Nothing-- step' _ _ Nothing = return Stop--{-# INLINE_NORMAL wordsBy #-}-wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-wordsBy predicate f (Stream step state) = Stream (stepOuter f) (Just state)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter (Fold fstep initial done) gst (Just st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- if predicate x- then return $ Skip (Just s)- else do- acc <- initial- acc' <- fstep acc x- go SPEC s acc'-- Skip s -> return $ Skip $ Just s- Stop -> return Stop-- where-- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if predicate x- then done acc >>= \r -> return $ Yield r (Just s)- else do- acc' <- fstep acc x- go SPEC s acc'- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r Nothing-- stepOuter _ _ Nothing = return Stop---- String search algorithms:--- http://www-igm.univ-mlv.fr/~lecroq/string/index.html--{---- TODO can we unify the splitting operations using a splitting configuration--- like in the split package.----data SplitStyle = Infix | Suffix | Prefix deriving (Eq, Show)--data SplitOptions = SplitOptions- { style :: SplitStyle- , withSep :: Bool -- ^ keep the separators in output- -- , compact :: Bool -- ^ treat multiple consecutive separators as one- -- , trimHead :: Bool -- ^ drop blank at head- -- , trimTail :: Bool -- ^ drop blank at tail- }--}--data SplitOnState s a =- GO_START- | GO_EMPTY_PAT s- | GO_SINGLE_PAT s a- | GO_SHORT_PAT s- | GO_KARP_RABIN s !(RB.Ring a) !(Ptr a)- | GO_DONE--{-# INLINE_NORMAL splitOn #-}-splitOn- :: forall m a b. (MonadIO m, Storable a, Enum a, Eq a)- => Array a- -> Fold m a b- -> Stream m a- -> Stream m b-splitOn patArr@Array{..} (Fold fstep initial done) (Stream step state) =- Stream stepOuter GO_START-- where-- patLen = A.length patArr- maxIndex = patLen - 1- elemBits = sizeOf (undefined :: a) * 8-- {-# INLINE_LATE stepOuter #-}- stepOuter _ GO_START =- if patLen == 0- then return $ Skip $ GO_EMPTY_PAT state- else if patLen == 1- then do- r <- liftIO $ (A.unsafeIndexIO patArr 0)- return $ Skip $ GO_SINGLE_PAT state r- else if sizeOf (undefined :: a) * patLen- <= sizeOf (undefined :: Word)- then return $ Skip $ GO_SHORT_PAT state- else do- (rb, rhead) <- liftIO $ RB.new patLen- return $ Skip $ GO_KARP_RABIN state rb rhead-- stepOuter gst (GO_SINGLE_PAT stt pat) = initial >>= go SPEC stt-- where-- go !_ st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- if pat == x- then do- r <- done acc- return $ Yield r (GO_SINGLE_PAT s pat)- else fstep acc x >>= go SPEC s- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r GO_DONE-- stepOuter gst (GO_SHORT_PAT stt) = initial >>= go0 SPEC 0 (0 :: Word) stt-- where-- mask :: Word- mask = (1 `shiftL` (elemBits * patLen)) - 1-- addToWord wrd a = (wrd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- patWord :: Word- patWord = mask .&. A.foldl' addToWord 0 patArr-- go0 !_ !idx wrd st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let wrd' = addToWord wrd x- if idx == maxIndex- then do- if wrd' .&. mask == patWord- then do- r <- done acc- return $ Yield r (GO_SHORT_PAT s)- else go1 SPEC wrd' s acc- else go0 SPEC (idx + 1) wrd' s acc- Skip s -> go0 SPEC idx wrd s acc- Stop -> do- acc' <- if idx /= 0- then go2 wrd idx acc- else return acc- done acc' >>= \r -> return $ Yield r GO_DONE-- {-# INLINE go1 #-}- go1 !_ wrd st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let wrd' = addToWord wrd x- old = (mask .&. wrd) `shiftR` (elemBits * (patLen - 1))- acc' <- fstep acc (toEnum $ fromIntegral old)- if wrd' .&. mask == patWord- then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)- else go1 SPEC wrd' s acc'- Skip s -> go1 SPEC wrd s acc- Stop -> do- acc' <- go2 wrd patLen acc- done acc' >>= \r -> return $ Yield r GO_DONE-- go2 !wrd !n !acc | n > 0 = do- let old = (mask .&. wrd) `shiftR` (elemBits * (n - 1))- fstep acc (toEnum $ fromIntegral old) >>= go2 wrd (n - 1)- go2 _ _ acc = return acc-- stepOuter gst (GO_KARP_RABIN stt rb rhead) = do- initial >>= go0 SPEC 0 rhead stt-- where-- k = 2891336453 :: Word32- coeff = k ^ patLen- addCksum cksum a = cksum * k + fromIntegral (fromEnum a)- deltaCksum cksum old new =- addCksum cksum new - coeff * fromIntegral (fromEnum old)-- -- XXX shall we use a random starting hash or 1 instead of 0?- patHash = A.foldl' addCksum 0 patArr-- -- rh == ringHead- go0 !_ !idx !rh st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- rh' <- liftIO $ RB.unsafeInsert rb rh x- if idx == maxIndex- then do- let fold = RB.unsafeFoldRing (RB.ringBound rb)- let !ringHash = fold addCksum 0 rb- if ringHash == patHash- then go2 SPEC ringHash rh' s acc- else go1 SPEC ringHash rh' s acc- else go0 SPEC (idx + 1) rh' s acc- Skip s -> go0 SPEC idx rh s acc- Stop -> do- !acc' <- if idx /= 0- then RB.unsafeFoldRingM rh fstep acc rb- else return acc- done acc' >>= \r -> return $ Yield r GO_DONE-- -- XXX Theoretically this code can do 4 times faster if GHC generates- -- optimal code. If we use just "(cksum' == patHash)" condition it goes- -- 4x faster, as soon as we add the "RB.unsafeEqArray rb v" condition- -- the generated code changes drastically and becomes 4x slower. Need- -- to investigate what is going on with GHC.- {-# INLINE go1 #-}- go1 !_ !cksum !rh st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- old <- liftIO $ peek rh- let cksum' = deltaCksum cksum old x- acc' <- fstep acc old-- if (cksum' == patHash)- then do- rh' <- liftIO (RB.unsafeInsert rb rh x)- go2 SPEC cksum' rh' s acc'- else do- rh' <- liftIO (RB.unsafeInsert rb rh x)- go1 SPEC cksum' rh' s acc'- Skip s -> go1 SPEC cksum rh s acc- Stop -> do- acc' <- RB.unsafeFoldRingFullM rh fstep acc rb- done acc' >>= \r -> return $ Yield r GO_DONE-- go2 !_ !cksum' !rh' s !acc' = do- if RB.unsafeEqArray rb rh' patArr- then do- r <- done acc'- return $ Yield r (GO_KARP_RABIN s rb rhead)- else go1 SPEC cksum' rh' s acc'-- stepOuter gst (GO_EMPTY_PAT st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- acc' <- fstep acc x- done acc' >>= \r -> return $ Yield r (GO_EMPTY_PAT s)- Skip s -> return $ Skip (GO_EMPTY_PAT s)- Stop -> return Stop-- stepOuter _ GO_DONE = return Stop--{-# INLINE_NORMAL splitSuffixOn #-}-splitSuffixOn- :: forall m a b. (MonadIO m, Storable a, Enum a, Eq a)- => Bool- -> Array a- -> Fold m a b- -> Stream m a- -> Stream m b-splitSuffixOn withSep patArr@Array{..} (Fold fstep initial done)- (Stream step state) =- Stream stepOuter GO_START-- where-- patLen = A.length patArr- maxIndex = patLen - 1- elemBits = sizeOf (undefined :: a) * 8-- {-# INLINE_LATE stepOuter #-}- stepOuter _ GO_START =- if patLen == 0- then return $ Skip $ GO_EMPTY_PAT state- else if patLen == 1- then do- r <- liftIO $ (A.unsafeIndexIO patArr 0)- return $ Skip $ GO_SINGLE_PAT state r- else if sizeOf (undefined :: a) * patLen- <= sizeOf (undefined :: Word)- then return $ Skip $ GO_SHORT_PAT state- else do- (rb, rhead) <- liftIO $ RB.new patLen- return $ Skip $ GO_KARP_RABIN state rb rhead-- stepOuter gst (GO_SINGLE_PAT stt pat) = do- -- This first part is the only difference between splitOn and- -- splitSuffixOn.- -- If the last element is a separator do not issue a blank segment.- res <- step (adaptState gst) stt- case res of- Yield x s -> do- acc <- initial- if pat == x- then do- acc' <- if withSep then fstep acc x else return acc- done acc' >>= \r -> return $ Yield r (GO_SINGLE_PAT s pat)- else fstep acc x >>= go SPEC s- Skip s -> return $ Skip $ (GO_SINGLE_PAT s pat)- Stop -> return Stop-- where-- -- This is identical for splitOn and splitSuffixOn- go !_ st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- if pat == x- then do- acc' <- if withSep then fstep acc x else return acc- r <- done acc'- return $ Yield r (GO_SINGLE_PAT s pat)- else fstep acc x >>= go SPEC s- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r GO_DONE-- stepOuter gst (GO_SHORT_PAT stt) = do-- -- Call "initial" only if the stream yields an element, otherwise we- -- may call "initial" but never yield anything. initial may produce a- -- side effect, therefore we will end up doing and discard a side- -- effect.-- let idx = 0- let wrd = 0- res <- step (adaptState gst) stt- case res of- Yield x s -> do- acc <- initial- let wrd' = addToWord wrd x- acc' <- if withSep then fstep acc x else return acc- if idx == maxIndex- then do- if wrd' .&. mask == patWord- then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)- else go0 SPEC (idx + 1) wrd' s acc'- else go0 SPEC (idx + 1) wrd' s acc'- Skip s -> return $ Skip (GO_SHORT_PAT s)- Stop -> return Stop-- where-- mask :: Word- mask = (1 `shiftL` (elemBits * patLen)) - 1-- addToWord wrd a = (wrd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- patWord :: Word- patWord = mask .&. A.foldl' addToWord 0 patArr-- go0 !_ !idx wrd st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let wrd' = addToWord wrd x- acc' <- if withSep then fstep acc x else return acc- if idx == maxIndex- then do- if wrd' .&. mask == patWord- then do- r <- done acc'- return $ Yield r (GO_SHORT_PAT s)- else go1 SPEC wrd' s acc'- else go0 SPEC (idx + 1) wrd' s acc'- Skip s -> go0 SPEC idx wrd s acc- Stop -> do- if (idx == maxIndex) && (wrd .&. mask == patWord)- then return Stop- else do- acc' <- if idx /= 0 && not withSep- then go2 wrd idx acc- else return acc- done acc' >>= \r -> return $ Yield r GO_DONE-- {-# INLINE go1 #-}- go1 !_ wrd st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let wrd' = addToWord wrd x- old = (mask .&. wrd) `shiftR` (elemBits * (patLen - 1))- acc' <- if withSep- then fstep acc x- else fstep acc (toEnum $ fromIntegral old)- if wrd' .&. mask == patWord- then done acc' >>= \r -> return $ Yield r (GO_SHORT_PAT s)- else go1 SPEC wrd' s acc'- Skip s -> go1 SPEC wrd s acc- Stop ->- -- If the last sequence is a separator do not issue a blank- -- segment.- if wrd .&. mask == patWord- then return Stop- else do- acc' <- if withSep- then return acc- else go2 wrd patLen acc- done acc' >>= \r -> return $ Yield r GO_DONE-- go2 !wrd !n !acc | n > 0 = do- let old = (mask .&. wrd) `shiftR` (elemBits * (n - 1))- fstep acc (toEnum $ fromIntegral old) >>= go2 wrd (n - 1)- go2 _ _ acc = return acc-- stepOuter gst (GO_KARP_RABIN stt rb rhead) = do- let idx = 0- res <- step (adaptState gst) stt- case res of- Yield x s -> do- acc <- initial- acc' <- if withSep then fstep acc x else return acc- rh' <- liftIO (RB.unsafeInsert rb rhead x)- if idx == maxIndex- then do- let fold = RB.unsafeFoldRing (RB.ringBound rb)- let !ringHash = fold addCksum 0 rb- if ringHash == patHash- then go2 SPEC ringHash rh' s acc'- else go0 SPEC (idx + 1) rh' s acc'- else go0 SPEC (idx + 1) rh' s acc'- Skip s -> return $ Skip (GO_KARP_RABIN s rb rhead)- Stop -> return Stop-- where-- k = 2891336453 :: Word32- coeff = k ^ patLen- addCksum cksum a = cksum * k + fromIntegral (fromEnum a)- deltaCksum cksum old new =- addCksum cksum new - coeff * fromIntegral (fromEnum old)-- -- XXX shall we use a random starting hash or 1 instead of 0?- patHash = A.foldl' addCksum 0 patArr-- -- rh == ringHead- go0 !_ !idx !rh st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc' <- if withSep then fstep acc x else return acc- rh' <- liftIO (RB.unsafeInsert rb rh x)- if idx == maxIndex- then do- let fold = RB.unsafeFoldRing (RB.ringBound rb)- let !ringHash = fold addCksum 0 rb- if ringHash == patHash- then go2 SPEC ringHash rh' s acc'- else go1 SPEC ringHash rh' s acc'- else go0 SPEC (idx + 1) rh' s acc'- Skip s -> go0 SPEC idx rh s acc- Stop -> do- -- do not issue a blank segment when we end at pattern- if (idx == maxIndex) && RB.unsafeEqArray rb rh patArr- then return Stop- else do- !acc' <- if idx /= 0 && not withSep- then RB.unsafeFoldRingM rh fstep acc rb- else return acc- done acc' >>= \r -> return $ Yield r GO_DONE-- -- XXX Theoretically this code can do 4 times faster if GHC generates- -- optimal code. If we use just "(cksum' == patHash)" condition it goes- -- 4x faster, as soon as we add the "RB.unsafeEqArray rb v" condition- -- the generated code changes drastically and becomes 4x slower. Need- -- to investigate what is going on with GHC.- {-# INLINE go1 #-}- go1 !_ !cksum !rh st !acc = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- old <- liftIO $ peek rh- let cksum' = deltaCksum cksum old x- acc' <- if withSep- then fstep acc x- else fstep acc old-- if (cksum' == patHash)- then do- rh' <- liftIO (RB.unsafeInsert rb rh x)- go2 SPEC cksum' rh' s acc'- else do- rh' <- liftIO (RB.unsafeInsert rb rh x)- go1 SPEC cksum' rh' s acc'- Skip s -> go1 SPEC cksum rh s acc- Stop -> do- if RB.unsafeEqArray rb rh patArr- then return Stop- else do- acc' <- if withSep- then return acc- else RB.unsafeFoldRingFullM rh fstep acc rb- done acc' >>= \r -> return $ Yield r GO_DONE-- go2 !_ !cksum' !rh' s !acc' = do- if RB.unsafeEqArray rb rh' patArr- then do- r <- done acc'- return $ Yield r (GO_KARP_RABIN s rb rhead)- else go1 SPEC cksum' rh' s acc'-- stepOuter gst (GO_EMPTY_PAT st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- acc <- initial- acc' <- fstep acc x- done acc' >>= \r -> return $ Yield r (GO_EMPTY_PAT s)- Skip s -> return $ Skip (GO_EMPTY_PAT s)- Stop -> return Stop-- stepOuter _ GO_DONE = return Stop--data SplitState s arr- = SplitInitial s- | SplitBuffering s arr- | SplitSplitting s arr- | SplitYielding arr (SplitState s arr)- | SplitFinishing---- XXX An alternative approach would be to use a partial fold (Fold m a b) to--- split using a splitBy like combinator. The Fold would consume upto the--- separator and return any leftover which can then be fed to the next fold.------ We can revisit this once we have partial folds/parsers.------ | Performs infix separator style splitting.-{-# INLINE_NORMAL splitInnerBy #-}-splitInnerBy- :: Monad m- => (f a -> m (f a, Maybe (f a))) -- splitter- -> (f a -> f a -> m (f a)) -- joiner- -> Stream m (f a)- -> Stream m (f a)-splitInnerBy splitter joiner (Stream step1 state1) =- (Stream step (SplitInitial state1))-- where-- {-# INLINE_LATE step #-}- step gst (SplitInitial st) = do- r <- step1 gst st- case r of- Yield x s -> do- (x1, mx2) <- splitter x- return $ case mx2 of- Nothing -> Skip (SplitBuffering s x1)- Just x2 -> Skip (SplitYielding x1 (SplitSplitting s x2))- Skip s -> return $ Skip (SplitInitial s)- Stop -> return $ Stop-- step gst (SplitBuffering st buf) = do- r <- step1 gst st- case r of- Yield x s -> do- (x1, mx2) <- splitter x- buf' <- joiner buf x1- return $ case mx2 of- Nothing -> Skip (SplitBuffering s buf')- Just x2 -> Skip (SplitYielding buf' (SplitSplitting s x2))- Skip s -> return $ Skip (SplitBuffering s buf)- Stop -> return $ Skip (SplitYielding buf SplitFinishing)-- step _ (SplitSplitting st buf) = do- (x1, mx2) <- splitter buf- return $ case mx2 of- Nothing -> Skip $ SplitBuffering st x1- Just x2 -> Skip $ SplitYielding x1 (SplitSplitting st x2)-- step _ (SplitYielding x next) = return $ Yield x next- step _ SplitFinishing = return $ Stop---- | Performs infix separator style splitting.-{-# INLINE_NORMAL splitInnerBySuffix #-}-splitInnerBySuffix- :: (Monad m, Eq (f a), Monoid (f a))- => (f a -> m (f a, Maybe (f a))) -- splitter- -> (f a -> f a -> m (f a)) -- joiner- -> Stream m (f a)- -> Stream m (f a)-splitInnerBySuffix splitter joiner (Stream step1 state1) =- (Stream step (SplitInitial state1))-- where-- {-# INLINE_LATE step #-}- step gst (SplitInitial st) = do- r <- step1 gst st- case r of- Yield x s -> do- (x1, mx2) <- splitter x- return $ case mx2 of- Nothing -> Skip (SplitBuffering s x1)- Just x2 -> Skip (SplitYielding x1 (SplitSplitting s x2))- Skip s -> return $ Skip (SplitInitial s)- Stop -> return $ Stop-- step gst (SplitBuffering st buf) = do- r <- step1 gst st- case r of- Yield x s -> do- (x1, mx2) <- splitter x- buf' <- joiner buf x1- return $ case mx2 of- Nothing -> Skip (SplitBuffering s buf')- Just x2 -> Skip (SplitYielding buf' (SplitSplitting s x2))- Skip s -> return $ Skip (SplitBuffering s buf)- Stop -> return $- if buf == mempty- then Stop- else Skip (SplitYielding buf SplitFinishing)-- step _ (SplitSplitting st buf) = do- (x1, mx2) <- splitter buf- return $ case mx2 of- Nothing -> Skip $ SplitBuffering st x1- Just x2 -> Skip $ SplitYielding x1 (SplitSplitting st x2)-- step _ (SplitYielding x next) = return $ Yield x next- step _ SplitFinishing = return $ Stop----------------------------------------------------------------------------------- Substreams---------------------------------------------------------------------------------{-# INLINE_NORMAL isPrefixOf #-}-isPrefixOf :: (Eq a, Monad m) => Stream m a -> Stream m a -> m Bool-isPrefixOf (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)- where- go (sa, sb, Nothing) = do- r <- stepa defState sa- case r of- Yield x sa' -> go (sa', sb, Just x)- Skip sa' -> go (sa', sb, Nothing)- Stop -> return True-- go (sa, sb, Just x) = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go (sa, sb', Nothing)- else return False- Skip sb' -> go (sa, sb', Just x)- Stop -> return False--{-# INLINE_NORMAL isSubsequenceOf #-}-isSubsequenceOf :: (Eq a, Monad m) => Stream m a -> Stream m a -> m Bool-isSubsequenceOf (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)- where- go (sa, sb, Nothing) = do- r <- stepa defState sa- case r of- Yield x sa' -> go (sa', sb, Just x)- Skip sa' -> go (sa', sb, Nothing)- Stop -> return True-- go (sa, sb, Just x) = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go (sa, sb', Nothing)- else go (sa, sb', Just x)- Skip sb' -> go (sa, sb', Just x)- Stop -> return False--{-# INLINE_NORMAL stripPrefix #-}-stripPrefix- :: (Eq a, Monad m)- => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripPrefix (Stream stepa ta) (Stream stepb tb) = go (ta, tb, Nothing)- where- go (sa, sb, Nothing) = do- r <- stepa defState sa- case r of- Yield x sa' -> go (sa', sb, Just x)- Skip sa' -> go (sa', sb, Nothing)- Stop -> return $ Just (Stream stepb sb)-- go (sa, sb, Just x) = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go (sa, sb', Nothing)- else return Nothing- Skip sb' -> go (sa, sb', Just x)- Stop -> return Nothing----------------------------------------------------------------------------------- Map and Fold----------------------------------------------------------------------------------- | Execute a monadic action for each element of the 'Stream'-{-# INLINE_NORMAL mapM_ #-}-mapM_ :: Monad m => (a -> m b) -> Stream m a -> m ()-mapM_ m = drain . mapM m------------------------------------------------------------------------------------ Stream transformations using Unfolds------------------------------------------------------------------------------------ Define a unique structure to use in inspection testing-data ConcatMapUState o i =- ConcatMapUOuter o- | ConcatMapUInner o i---- | @concatMapU unfold stream@ uses @unfold@ to map the input stream elements--- to streams and then flattens the generated streams into a single output--- stream.---- This is like 'concatMap' but uses an unfold with an explicit state to--- generate the stream instead of a 'Stream' type generator. This allows better--- optimization via fusion. This can be many times more efficient than--- 'concatMap'.--{-# INLINE_NORMAL concatMapU #-}-concatMapU :: Monad m => Unfold m a b -> Stream m a -> Stream m b-concatMapU (Unfold istep inject) (Stream ostep ost) =- Stream step (ConcatMapUOuter ost)- where- {-# INLINE_LATE step #-}- step gst (ConcatMapUOuter o) = do- r <- ostep (adaptState gst) o- case r of- Yield a o' -> do- i <- inject a- i `seq` return (Skip (ConcatMapUInner o' i))- Skip o' -> return $ Skip (ConcatMapUOuter o')- Stop -> return $ Stop-- step _ (ConcatMapUInner o i) = do- r <- istep i- return $ case r of- Yield x i' -> Yield x (ConcatMapUInner o i')- Skip i' -> Skip (ConcatMapUInner o i')- Stop -> Skip (ConcatMapUOuter o)--data ConcatUnfoldInterleaveState o i =- ConcatUnfoldInterleaveOuter o [i]- | ConcatUnfoldInterleaveInner o [i]- | ConcatUnfoldInterleaveInnerL [i] [i]- | ConcatUnfoldInterleaveInnerR [i] [i]---- XXX use arrays to store state instead of lists.--- XXX In general we can use different scheduling strategies e.g. how to--- schedule the outer vs inner loop or assigning weights to different streams--- or outer and inner loops.---- After a yield, switch to the next stream. Do not switch streams on Skip.--- Yield from outer stream switches to the inner stream.------ There are two choices here, (1) exhaust the outer stream first and then--- start yielding from the inner streams, this is much simpler to implement,--- (2) yield at least one element from an inner stream before going back to--- outer stream and opening the next stream from it.------ Ideally, we need some scheduling bias to inner streams vs outer stream.--- Maybe we can configure the behavior.----{-# INLINE_NORMAL concatUnfoldInterleave #-}-concatUnfoldInterleave :: Monad m => Unfold m a b -> Stream m a -> Stream m b-concatUnfoldInterleave (Unfold istep inject) (Stream ostep ost) =- Stream step (ConcatUnfoldInterleaveOuter ost [])- where- {-# INLINE_LATE step #-}- step gst (ConcatUnfoldInterleaveOuter o ls) = do- r <- ostep (adaptState gst) o- case r of- Yield a o' -> do- i <- inject a- i `seq` return (Skip (ConcatUnfoldInterleaveInner o' (i : ls)))- Skip o' -> return $ Skip (ConcatUnfoldInterleaveOuter o' ls)- Stop -> return $ Skip (ConcatUnfoldInterleaveInnerL ls [])-- step _ (ConcatUnfoldInterleaveInner _ []) = undefined- step _ (ConcatUnfoldInterleaveInner o (st:ls)) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveOuter o (s:ls))- Skip s -> Skip (ConcatUnfoldInterleaveInner o (s:ls))- Stop -> Skip (ConcatUnfoldInterleaveOuter o ls)-- step _ (ConcatUnfoldInterleaveInnerL [] []) = return Stop- step _ (ConcatUnfoldInterleaveInnerL [] rs) =- return $ Skip (ConcatUnfoldInterleaveInnerR [] rs)-- step _ (ConcatUnfoldInterleaveInnerL (st:ls) rs) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveInnerL ls (s:rs))- Skip s -> Skip (ConcatUnfoldInterleaveInnerL (s:ls) rs)- Stop -> Skip (ConcatUnfoldInterleaveInnerL ls rs)-- step _ (ConcatUnfoldInterleaveInnerR [] []) = return Stop- step _ (ConcatUnfoldInterleaveInnerR ls []) =- return $ Skip (ConcatUnfoldInterleaveInnerL ls [])-- step _ (ConcatUnfoldInterleaveInnerR ls (st:rs)) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveInnerR (s:ls) rs)- Skip s -> Skip (ConcatUnfoldInterleaveInnerR ls (s:rs))- Stop -> Skip (ConcatUnfoldInterleaveInnerR ls rs)---- XXX In general we can use different scheduling strategies e.g. how to--- schedule the outer vs inner loop or assigning weights to different streams--- or outer and inner loops.------ This could be inefficient if the tasks are too small.------ Compared to concatUnfoldInterleave this one switches streams on Skips.----{-# INLINE_NORMAL concatUnfoldRoundrobin #-}-concatUnfoldRoundrobin :: Monad m => Unfold m a b -> Stream m a -> Stream m b-concatUnfoldRoundrobin (Unfold istep inject) (Stream ostep ost) =- Stream step (ConcatUnfoldInterleaveOuter ost [])- where- {-# INLINE_LATE step #-}- step gst (ConcatUnfoldInterleaveOuter o ls) = do- r <- ostep (adaptState gst) o- case r of- Yield a o' -> do- i <- inject a- i `seq` return (Skip (ConcatUnfoldInterleaveInner o' (i : ls)))- Skip o' -> return $ Skip (ConcatUnfoldInterleaveInner o' ls)- Stop -> return $ Skip (ConcatUnfoldInterleaveInnerL ls [])-- step _ (ConcatUnfoldInterleaveInner o []) =- return $ Skip (ConcatUnfoldInterleaveOuter o [])-- step _ (ConcatUnfoldInterleaveInner o (st:ls)) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveOuter o (s:ls))- Skip s -> Skip (ConcatUnfoldInterleaveOuter o (s:ls))- Stop -> Skip (ConcatUnfoldInterleaveOuter o ls)-- step _ (ConcatUnfoldInterleaveInnerL [] []) = return Stop- step _ (ConcatUnfoldInterleaveInnerL [] rs) =- return $ Skip (ConcatUnfoldInterleaveInnerR [] rs)-- step _ (ConcatUnfoldInterleaveInnerL (st:ls) rs) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveInnerL ls (s:rs))- Skip s -> Skip (ConcatUnfoldInterleaveInnerL ls (s:rs))- Stop -> Skip (ConcatUnfoldInterleaveInnerL ls rs)-- step _ (ConcatUnfoldInterleaveInnerR [] []) = return Stop- step _ (ConcatUnfoldInterleaveInnerR ls []) =- return $ Skip (ConcatUnfoldInterleaveInnerL ls [])-- step _ (ConcatUnfoldInterleaveInnerR ls (st:rs)) = do- r <- istep st- return $ case r of- Yield x s -> Yield x (ConcatUnfoldInterleaveInnerR (s:ls) rs)- Skip s -> Skip (ConcatUnfoldInterleaveInnerR (s:ls) rs)- Stop -> Skip (ConcatUnfoldInterleaveInnerR ls rs)--data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2---- Note that this could be much faster compared to the CPS stream. However, as--- the number of streams being composed increases this may become expensive.--- Need to see where the breaking point is between the two.----{-# INLINE_NORMAL append #-}-append :: Monad m => Stream m a -> Stream m a -> Stream m a-append (Stream step1 state1) (Stream step2 state2) =- Stream step (AppendFirst state1)-- where-- {-# INLINE_LATE step #-}- step gst (AppendFirst st) = do- r <- step1 gst st- return $ case r of- Yield a s -> Yield a (AppendFirst s)- Skip s -> Skip (AppendFirst s)- Stop -> Skip (AppendSecond state2)-- step gst (AppendSecond st) = do- r <- step2 gst st- return $ case r of- Yield a s -> Yield a (AppendSecond s)- Skip s -> Skip (AppendSecond s)- Stop -> Stop--data InterleaveState s1 s2 = InterleaveFirst s1 s2 | InterleaveSecond s1 s2- | InterleaveSecondOnly s2 | InterleaveFirstOnly s1--{-# INLINE_NORMAL interleave #-}-interleave :: Monad m => Stream m a -> Stream m a -> Stream m a-interleave (Stream step1 state1) (Stream step2 state2) =- Stream step (InterleaveFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (InterleaveFirst st1 st2) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveSecond s st2)- Skip s -> Skip (InterleaveFirst s st2)- Stop -> Skip (InterleaveSecondOnly st2)-- step gst (InterleaveSecond st1 st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveFirst st1 s)- Skip s -> Skip (InterleaveSecond st1 s)- Stop -> Skip (InterleaveFirstOnly st1)-- step gst (InterleaveFirstOnly st1) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveFirstOnly s)- Skip s -> Skip (InterleaveFirstOnly s)- Stop -> Stop-- step gst (InterleaveSecondOnly st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveSecondOnly s)- Skip s -> Skip (InterleaveSecondOnly s)- Stop -> Stop--{-# INLINE_NORMAL interleaveMin #-}-interleaveMin :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveMin (Stream step1 state1) (Stream step2 state2) =- Stream step (InterleaveFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (InterleaveFirst st1 st2) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveSecond s st2)- Skip s -> Skip (InterleaveFirst s st2)- Stop -> Stop-- step gst (InterleaveSecond st1 st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveFirst st1 s)- Skip s -> Skip (InterleaveSecond st1 s)- Stop -> Stop-- step _ (InterleaveFirstOnly _) = undefined- step _ (InterleaveSecondOnly _) = undefined--{-# INLINE_NORMAL interleaveSuffix #-}-interleaveSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveSuffix (Stream step1 state1) (Stream step2 state2) =- Stream step (InterleaveFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (InterleaveFirst st1 st2) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveSecond s st2)- Skip s -> Skip (InterleaveFirst s st2)- Stop -> Stop-- step gst (InterleaveSecond st1 st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveFirst st1 s)- Skip s -> Skip (InterleaveSecond st1 s)- Stop -> Skip (InterleaveFirstOnly st1)-- step gst (InterleaveFirstOnly st1) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveFirstOnly s)- Skip s -> Skip (InterleaveFirstOnly s)- Stop -> Stop-- step _ (InterleaveSecondOnly _) = undefined--data InterleaveInfixState s1 s2 a- = InterleaveInfixFirst s1 s2- | InterleaveInfixSecondBuf s1 s2- | InterleaveInfixSecondYield s1 s2 a- | InterleaveInfixFirstYield s1 s2 a- | InterleaveInfixFirstOnly s1--{-# INLINE_NORMAL interleaveInfix #-}-interleaveInfix :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveInfix (Stream step1 state1) (Stream step2 state2) =- Stream step (InterleaveInfixFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (InterleaveInfixFirst st1 st2) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveInfixSecondBuf s st2)- Skip s -> Skip (InterleaveInfixFirst s st2)- Stop -> Stop-- step gst (InterleaveInfixSecondBuf st1 st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Skip (InterleaveInfixSecondYield st1 s a)- Skip s -> Skip (InterleaveInfixSecondBuf st1 s)- Stop -> Skip (InterleaveInfixFirstOnly st1)-- step gst (InterleaveInfixSecondYield st1 st2 x) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield x (InterleaveInfixFirstYield s st2 a)- Skip s -> Skip (InterleaveInfixSecondYield s st2 x)- Stop -> Stop-- step _ (InterleaveInfixFirstYield st1 st2 x) = do- return $ Yield x (InterleaveInfixSecondBuf st1 st2)-- step gst (InterleaveInfixFirstOnly st1) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveInfixFirstOnly s)- Skip s -> Skip (InterleaveInfixFirstOnly s)- Stop -> Stop--{-# INLINE_NORMAL roundRobin #-}-roundRobin :: Monad m => Stream m a -> Stream m a -> Stream m a-roundRobin (Stream step1 state1) (Stream step2 state2) =- Stream step (InterleaveFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (InterleaveFirst st1 st2) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveSecond s st2)- Skip s -> Skip (InterleaveSecond s st2)- Stop -> Skip (InterleaveSecondOnly st2)-- step gst (InterleaveSecond st1 st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveFirst st1 s)- Skip s -> Skip (InterleaveFirst st1 s)- Stop -> Skip (InterleaveFirstOnly st1)-- step gst (InterleaveSecondOnly st2) = do- r <- step2 gst st2- return $ case r of- Yield a s -> Yield a (InterleaveSecondOnly s)- Skip s -> Skip (InterleaveSecondOnly s)- Stop -> Stop-- step gst (InterleaveFirstOnly st1) = do- r <- step1 gst st1- return $ case r of- Yield a s -> Yield a (InterleaveFirstOnly s)- Skip s -> Skip (InterleaveFirstOnly s)- Stop -> Stop--data ICUState s1 s2 i1 i2 =- ICUFirst s1 s2- | ICUSecond s1 s2- | ICUSecondOnly s2- | ICUFirstOnly s1- | ICUFirstInner s1 s2 i1- | ICUSecondInner s1 s2 i2- | ICUFirstOnlyInner s1 i1- | ICUSecondOnlyInner s2 i2---- | Interleave streams (full streams, not the elements) unfolded from two--- input streams and concat. Stop when the first stream stops. If the second--- stream ends before the first one then first stream still keeps running alone--- without any interleaving with the second stream.------ [a1, a2, ... an] [b1, b2 ...]--- => [streamA1, streamA2, ... streamAn] [streamB1, streamB2, ...]--- => [streamA1, streamB1, streamA2...StreamAn, streamBn]--- => [a11, a12, ...a1j, b11, b12, ...b1k, a21, a22, ...]----{-# INLINE_NORMAL gintercalateSuffix #-}-gintercalateSuffix- :: Monad m- => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c-gintercalateSuffix- (Unfold istep1 inject1) (Stream step1 state1)- (Unfold istep2 inject2) (Stream step2 state2) =- Stream step (ICUFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (ICUFirst s1 s2) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (ICUFirstInner s s2 i))- Skip s -> return $ Skip (ICUFirst s s2)- Stop -> return Stop-- step gst (ICUFirstOnly s1) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (ICUFirstOnlyInner s i))- Skip s -> return $ Skip (ICUFirstOnly s)- Stop -> return Stop-- step _ (ICUFirstInner s1 s2 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (ICUFirstInner s1 s2 i')- Skip i' -> Skip (ICUFirstInner s1 s2 i')- Stop -> Skip (ICUSecond s1 s2)-- step _ (ICUFirstOnlyInner s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (ICUFirstOnlyInner s1 i')- Skip i' -> Skip (ICUFirstOnlyInner s1 i')- Stop -> Skip (ICUFirstOnly s1)-- step gst (ICUSecond s1 s2) = do- r <- step2 (adaptState gst) s2- case r of- Yield a s -> do- i <- inject2 a- i `seq` return (Skip (ICUSecondInner s1 s i))- Skip s -> return $ Skip (ICUSecond s1 s)- Stop -> return $ Skip (ICUFirstOnly s1)-- step _ (ICUSecondInner s1 s2 i2) = do- r <- istep2 i2- return $ case r of- Yield x i' -> Yield x (ICUSecondInner s1 s2 i')- Skip i' -> Skip (ICUSecondInner s1 s2 i')- Stop -> Skip (ICUFirst s1 s2)-- step _ (ICUSecondOnly _s2) = undefined- step _ (ICUSecondOnlyInner _s2 _i2) = undefined--data InterposeSuffixState s1 i1 =- InterposeSuffixFirst s1- -- | InterposeSuffixFirstYield s1 i1- | InterposeSuffixFirstInner s1 i1- | InterposeSuffixSecond s1---- Note that if an unfolded layer turns out to be nil we still emit the--- separator effect. An alternate behavior could be to emit the separator--- effect only if at least one element has been yielded by the unfolding.--- However, that becomes a bit complicated, so we have chosen the former--- behvaior for now.-{-# INLINE_NORMAL interposeSuffix #-}-interposeSuffix- :: Monad m- => m c -> Unfold m b c -> Stream m b -> Stream m c-interposeSuffix- action- (Unfold istep1 inject1) (Stream step1 state1) =- Stream step (InterposeSuffixFirst state1)-- where-- {-# INLINE_LATE step #-}- step gst (InterposeSuffixFirst s1) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (InterposeSuffixFirstInner s i))- -- i `seq` return (Skip (InterposeSuffixFirstYield s i))- Skip s -> return $ Skip (InterposeSuffixFirst s)- Stop -> return Stop-- {-- step _ (InterposeSuffixFirstYield s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (InterposeSuffixFirstInner s1 i')- Skip i' -> Skip (InterposeSuffixFirstYield s1 i')- Stop -> Skip (InterposeSuffixFirst s1)- -}-- step _ (InterposeSuffixFirstInner s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (InterposeSuffixFirstInner s1 i')- Skip i' -> Skip (InterposeSuffixFirstInner s1 i')- Stop -> Skip (InterposeSuffixSecond s1)-- step _ (InterposeSuffixSecond s1) = do- r <- action- return $ Yield r (InterposeSuffixFirst s1)--data ICALState s1 s2 i1 i2 a =- ICALFirst s1 s2- -- | ICALFirstYield s1 s2 i1- | ICALFirstInner s1 s2 i1- | ICALFirstOnly s1- | ICALFirstOnlyInner s1 i1- | ICALSecondInject s1 s2- | ICALFirstInject s1 s2 i2- -- | ICALFirstBuf s1 s2 i1 i2- | ICALSecondInner s1 s2 i1 i2- -- -- | ICALSecondInner s1 s2 i1 i2 a- -- -- | ICALFirstResume s1 s2 i1 i2 a---- | Interleave streams (full streams, not the elements) unfolded from two--- input streams and concat. Stop when the first stream stops. If the second--- stream ends before the first one then first stream still keeps running alone--- without any interleaving with the second stream.------ [a1, a2, ... an] [b1, b2 ...]--- => [streamA1, streamA2, ... streamAn] [streamB1, streamB2, ...]--- => [streamA1, streamB1, streamA2...StreamAn, streamBn]--- => [a11, a12, ...a1j, b11, b12, ...b1k, a21, a22, ...]----{-# INLINE_NORMAL gintercalate #-}-gintercalate- :: Monad m- => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c-gintercalate- (Unfold istep1 inject1) (Stream step1 state1)- (Unfold istep2 inject2) (Stream step2 state2) =- Stream step (ICALFirst state1 state2)-- where-- {-# INLINE_LATE step #-}- step gst (ICALFirst s1 s2) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (ICALFirstInner s s2 i))- -- i `seq` return (Skip (ICALFirstYield s s2 i))- Skip s -> return $ Skip (ICALFirst s s2)- Stop -> return Stop-- {-- step _ (ICALFirstYield s1 s2 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (ICALFirstInner s1 s2 i')- Skip i' -> Skip (ICALFirstYield s1 s2 i')- Stop -> Skip (ICALFirst s1 s2)- -}-- step _ (ICALFirstInner s1 s2 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (ICALFirstInner s1 s2 i')- Skip i' -> Skip (ICALFirstInner s1 s2 i')- Stop -> Skip (ICALSecondInject s1 s2)-- step gst (ICALFirstOnly s1) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (ICALFirstOnlyInner s i))- Skip s -> return $ Skip (ICALFirstOnly s)- Stop -> return Stop-- step _ (ICALFirstOnlyInner s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (ICALFirstOnlyInner s1 i')- Skip i' -> Skip (ICALFirstOnlyInner s1 i')- Stop -> Skip (ICALFirstOnly s1)-- -- We inject the second stream even before checking if the first stream- -- would yield any more elements. There is no clear choice whether we- -- should do this before or after that. Doing it after may make the state- -- machine a bit simpler though.- step gst (ICALSecondInject s1 s2) = do- r <- step2 (adaptState gst) s2- case r of- Yield a s -> do- i <- inject2 a- i `seq` return (Skip (ICALFirstInject s1 s i))- Skip s -> return $ Skip (ICALSecondInject s1 s)- Stop -> return $ Skip (ICALFirstOnly s1)-- step gst (ICALFirstInject s1 s2 i2) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (ICALSecondInner s s2 i i2))- -- i `seq` return (Skip (ICALFirstBuf s s2 i i2))- Skip s -> return $ Skip (ICALFirstInject s s2 i2)- Stop -> return Stop-- {-- step _ (ICALFirstBuf s1 s2 i1 i2) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Skip (ICALSecondInner s1 s2 i' i2 x)- Skip i' -> Skip (ICALFirstBuf s1 s2 i' i2)- Stop -> Stop-- step _ (ICALSecondInner s1 s2 i1 i2 v) = do- r <- istep2 i2- return $ case r of- Yield x i' -> Yield x (ICALSecondInner s1 s2 i1 i' v)- Skip i' -> Skip (ICALSecondInner s1 s2 i1 i' v)- Stop -> Skip (ICALFirstResume s1 s2 i1 i2 v)- -}-- step _ (ICALSecondInner s1 s2 i1 i2) = do- r <- istep2 i2- return $ case r of- Yield x i' -> Yield x (ICALSecondInner s1 s2 i1 i')- Skip i' -> Skip (ICALSecondInner s1 s2 i1 i')- Stop -> Skip (ICALFirstInner s1 s2 i1)- -- Stop -> Skip (ICALFirstResume s1 s2 i1 i2)-- {-- step _ (ICALFirstResume s1 s2 i1 i2 x) = do- return $ Yield x (ICALFirstInner s1 s2 i1 i2)- -}--data InterposeState s1 i1 a =- InterposeFirst s1- -- | InterposeFirstYield s1 i1- | InterposeFirstInner s1 i1- | InterposeFirstInject s1- -- | InterposeFirstBuf s1 i1- | InterposeSecondYield s1 i1- -- -- | InterposeSecondYield s1 i1 a- -- -- | InterposeFirstResume s1 i1 a---- Note that this only interposes the pure values, we may run many effects to--- generate those values as some effects may not generate anything (Skip).-{-# INLINE_NORMAL interpose #-}-interpose :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c-interpose- action- (Unfold istep1 inject1) (Stream step1 state1) =- Stream step (InterposeFirst state1)-- where-- {-# INLINE_LATE step #-}- step gst (InterposeFirst s1) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- i `seq` return (Skip (InterposeFirstInner s i))- -- i `seq` return (Skip (InterposeFirstYield s i))- Skip s -> return $ Skip (InterposeFirst s)- Stop -> return Stop-- {-- step _ (InterposeFirstYield s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (InterposeFirstInner s1 i')- Skip i' -> Skip (InterposeFirstYield s1 i')- Stop -> Skip (InterposeFirst s1)- -}-- step _ (InterposeFirstInner s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Yield x (InterposeFirstInner s1 i')- Skip i' -> Skip (InterposeFirstInner s1 i')- Stop -> Skip (InterposeFirstInject s1)-- step gst (InterposeFirstInject s1) = do- r <- step1 (adaptState gst) s1- case r of- Yield a s -> do- i <- inject1 a- -- i `seq` return (Skip (InterposeFirstBuf s i))- i `seq` return (Skip (InterposeSecondYield s i))- Skip s -> return $ Skip (InterposeFirstInject s)- Stop -> return Stop-- {-- step _ (InterposeFirstBuf s1 i1) = do- r <- istep1 i1- return $ case r of- Yield x i' -> Skip (InterposeSecondYield s1 i' x)- Skip i' -> Skip (InterposeFirstBuf s1 i')- Stop -> Stop- -}-- {-- step _ (InterposeSecondYield s1 i1 v) = do- r <- action- return $ Yield r (InterposeFirstResume s1 i1 v)- -}- step _ (InterposeSecondYield s1 i1) = do- r <- action- return $ Yield r (InterposeFirstInner s1 i1)-- {-- step _ (InterposeFirstResume s1 i1 v) = do- return $ Yield v (InterposeFirstInner s1 i1)- -}----------------------------------------------------------------------------------- Exceptions---------------------------------------------------------------------------------data GbracketState s1 s2 v- = GBracketInit- | GBracketNormal s1 v- | GBracketException s2---- | The most general bracketing and exception combinator. All other--- combinators can be expressed in terms of this combinator. This can also be--- used for cases which are not covered by the standard combinators.------ /Internal/----{-# INLINE_NORMAL gbracket #-}-gbracket- :: Monad m- => m c -- ^ before- -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)- -> (c -> m d) -- ^ after, on normal stop- -> (c -> e -> Stream m b) -- ^ on exception- -> (c -> Stream m b) -- ^ stream generator- -> Stream m b-gbracket bef exc aft fexc fnormal =- Stream step GBracketInit-- where-- {-# INLINE_LATE step #-}- step _ GBracketInit = do- r <- bef- return $ Skip $ GBracketNormal (fnormal r) r-- step gst (GBracketNormal (UnStream step1 st) v) = do- res <- exc $ step1 gst st- case res of- Right r -> case r of- Yield x s ->- return $ Yield x (GBracketNormal (Stream step1 s) v)- Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)- Stop -> aft v >> return Stop- Left e -> return $ Skip (GBracketException (fexc v e))- step gst (GBracketException (UnStream step1 st)) = do- res <- step1 gst st- case res of- Yield x s -> return $ Yield x (GBracketException (Stream step1 s))- Skip s -> return $ Skip (GBracketException (Stream step1 s))- Stop -> return Stop---- | Run a side effect before the stream yields its first element.-{-# INLINE_NORMAL before #-}-before :: Monad m => m b -> Stream m a -> Stream m a-before action (Stream step state) = Stream step' Nothing-- where-- {-# INLINE_LATE step' #-}- step' _ Nothing = action >> return (Skip (Just state))-- step' gst (Just st) = do- res <- step gst st- case res of- Yield x s -> return $ Yield x (Just s)- Skip s -> return $ Skip (Just s)- Stop -> return Stop---- | Run a side effect whenever the stream stops normally.-{-# INLINE_NORMAL after #-}-after :: Monad m => m b -> Stream m a -> Stream m a-after action (Stream step state) = Stream step' state-- where-- {-# INLINE_LATE step' #-}- step' gst st = do- res <- step gst st- case res of- Yield x s -> return $ Yield x s- Skip s -> return $ Skip s- Stop -> action >> return Stop---- XXX These combinators are expensive due to the call to--- onException/handle/try on each step. Therefore, when possible, they should--- be called in an outer loop where we perform less iterations. For example, we--- cannot call them on each iteration in a char stream, instead we can call--- them when doing an IO on an array.------ XXX For high performance error checks in busy streams we may need another--- Error constructor in step.------ | Run a side effect whenever the stream aborts due to an exception. The--- exception is not caught, simply rethrown.-{-# INLINE_NORMAL onException #-}-onException :: MonadCatch m => m b -> Stream m a -> Stream m a-onException action str =- gbracket (return ()) MC.try return- (\_ (e :: MC.SomeException) -> nilM (action >> MC.throwM e))- (\_ -> str)--{-# INLINE_NORMAL _onException #-}-_onException :: MonadCatch m => m b -> Stream m a -> Stream m a-_onException action (Stream step state) = Stream step' state-- where-- {-# INLINE_LATE step' #-}- step' gst st = do- res <- step gst st `MC.onException` action- case res of- Yield x s -> return $ Yield x s- Skip s -> return $ Skip s- Stop -> return Stop---- XXX bracket is like concatMap, it generates a stream and then flattens it.--- Like concatMap it has 10x worse performance compared to linear fused--- compositions.------ | Run the first action before the stream starts and remember its output,--- generate a stream using the output, run the second action providing the--- remembered value as an argument whenever the stream ends normally or due to--- an exception.-{-# INLINE_NORMAL bracket #-}-bracket :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a-bracket bef aft bet =- gbracket bef MC.try aft- (\a (e :: SomeException) -> nilM (aft a >> MC.throwM e)) bet--data BracketState s v = BracketInit | BracketRun s v--{-# INLINE_NORMAL _bracket #-}-_bracket :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a-_bracket bef aft bet = Stream step' BracketInit-- where-- {-# INLINE_LATE step' #-}- step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))-- -- NOTE: It is important to use UnStream instead of the Stream pattern- -- here, otherwise we get huge perf degradation, see note in concatMap.- step' gst (BracketRun (UnStream step state) v) = do- -- res <- step gst state `MC.onException` aft v- res <- MC.try $ step gst state- case res of- Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop- Right r -> case r of- Yield x s -> return $ Yield x (BracketRun (Stream step s) v)- Skip s -> return $ Skip (BracketRun (Stream step s) v)- Stop -> aft v >> return Stop---- | Run a side effect whenever the stream stops normally or aborts due to an--- exception.-{-# INLINE finally #-}-finally :: MonadCatch m => m b -> Stream m a -> Stream m a--- finally action xs = after action $ onException action xs-finally action xs = bracket (return ()) (\_ -> action) (const xs)---- | When evaluating a stream if an exception occurs, stream evaluation aborts--- and the specified exception handler is run with the exception as argument.-{-# INLINE_NORMAL handle #-}-handle :: (MonadCatch m, Exception e)- => (e -> Stream m a) -> Stream m a -> Stream m a-handle f str =- gbracket (return ()) MC.try return (\_ e -> f e) (\_ -> str)--{-# INLINE_NORMAL _handle #-}-_handle :: (MonadCatch m, Exception e)- => (e -> Stream m a) -> Stream m a -> Stream m a-_handle f (Stream step state) = Stream step' (Left state)-- where-- {-# INLINE_LATE step' #-}- step' gst (Left st) = do- res <- MC.try $ step gst st- case res of- Left e -> return $ Skip $ Right (f e)- Right r -> case r of- Yield x s -> return $ Yield x (Left s)- Skip s -> return $ Skip (Left s)- Stop -> return Stop-- step' gst (Right (UnStream step1 st)) = do- res <- step1 gst st- case res of- Yield x s -> return $ Yield x (Right (Stream step1 s))- Skip s -> return $ Skip (Right (Stream step1 s))- Stop -> return Stop------------------------------------------------------------------------------------ General transformation----------------------------------------------------------------------------------{-# INLINE_NORMAL transform #-}-transform :: Monad m => Pipe m a b -> Stream m a -> Stream m b-transform (Pipe pstep1 pstep2 pstate) (Stream step state) =- Stream step' (Consume pstate, state)-- where-- {-# INLINE_LATE step' #-}-- step' gst (Consume pst, st) = pst `seq` do- r <- step (adaptState gst) st- case r of- Yield x s -> do- res <- pstep1 pst x- case res of- Pipe.Yield b pst' -> return $ Yield b (pst', s)- Pipe.Continue pst' -> return $ Skip (pst', s)- Skip s -> return $ Skip (Consume pst, s)- Stop -> return Stop-- step' _ (Produce pst, st) = pst `seq` do- res <- pstep2 pst- case res of- Pipe.Yield b pst' -> return $ Yield b (pst', st)- Pipe.Continue pst' -> return $ Skip (pst', st)----------------------------------------------------------------------------------- Transformation by Folding (Scans)------------------------------------------------------------------------------------------------------------------------------------------------------------------ Prescans----------------------------------------------------------------------------------- XXX Is a prescan useful, discarding the last step does not sound useful? I--- am not sure about the utility of this function, so this is implemented but--- not exposed. We can expose it if someone provides good reasons why this is--- useful.------ XXX We have to execute the stream one step ahead to know that we are at the--- last step. The vector implementation of prescan executes the last fold step--- but does not yield the result. This means we have executed the effect but--- discarded value. This does not sound right. In this implementation we are--- not executing the last fold step.-{-# INLINE_NORMAL prescanlM' #-}-prescanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b-prescanlM' f mz (Stream step state) = Stream step' (state, mz)- where- {-# INLINE_LATE step' #-}- step' gst (st, prev) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- acc <- prev- return $ Yield acc (s, f acc x)- Skip s -> return $ Skip (s, prev)- Stop -> return Stop--{-# INLINE prescanl' #-}-prescanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b-prescanl' f z = prescanlM' (\a b -> return (f a b)) (return z)----------------------------------------------------------------------------------- Monolithic postscans (postscan followed by a map)----------------------------------------------------------------------------------- The performance of a modular postscan followed by a map seems to be--- equivalent to this monolithic scan followed by map therefore we may not need--- this implementation. We just have it for performance comparison and in case--- modular version does not perform well in some situation.----{-# INLINE_NORMAL postscanlMx' #-}-postscanlMx' :: Monad m- => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b-postscanlMx' fstep begin done (Stream step state) = do- Stream step' (state, begin)- where- {-# INLINE_LATE step' #-}- step' gst (st, acc) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- old <- acc- y <- fstep old x- v <- done y- v `seq` y `seq` return (Yield v (s, return y))- Skip s -> return $ Skip (s, acc)- Stop -> return Stop--{-# INLINE_NORMAL postscanlx' #-}-postscanlx' :: Monad m- => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b-postscanlx' fstep begin done s =- postscanlMx' (\b a -> return (fstep b a)) (return begin) (return . done) s---- XXX do we need consM strict to evaluate the begin value?-{-# INLINE scanlMx' #-}-scanlMx' :: Monad m- => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b-scanlMx' fstep begin done s =- (begin >>= \x -> x `seq` done x) `consM` postscanlMx' fstep begin done s--{-# INLINE scanlx' #-}-scanlx' :: Monad m- => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b-scanlx' fstep begin done s =- scanlMx' (\b a -> return (fstep b a)) (return begin) (return . done) s----------------------------------------------------------------------------------- postscans---------------------------------------------------------------------------------{-# INLINE_NORMAL postscanlM' #-}-postscanlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b-postscanlM' fstep begin (Stream step state) =- begin `seq` Stream step' (state, begin)- where- {-# INLINE_LATE step' #-}- step' gst (st, acc) = acc `seq` do- r <- step (adaptState gst) st- case r of- Yield x s -> do- y <- fstep acc x- y `seq` return (Yield y (s, y))- Skip s -> return $ Skip (s, acc)- Stop -> return Stop--{-# INLINE_NORMAL postscanl' #-}-postscanl' :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a-postscanl' f = postscanlM' (\a b -> return (f a b))--{-# INLINE_NORMAL postscanlM #-}-postscanlM :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b-postscanlM fstep begin (Stream step state) = Stream step' (state, begin)- where- {-# INLINE_LATE step' #-}- step' gst (st, acc) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- y <- fstep acc x- return (Yield y (s, y))- Skip s -> return $ Skip (s, acc)- Stop -> return Stop--{-# INLINE_NORMAL postscanl #-}-postscanl :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a-postscanl f = postscanlM (\a b -> return (f a b))--{-# INLINE_NORMAL scanlM' #-}-scanlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b-scanlM' fstep begin s = begin `seq` (begin `cons` postscanlM' fstep begin s)--{-# INLINE scanl' #-}-scanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b-scanl' f = scanlM' (\a b -> return (f a b))--{-# INLINE_NORMAL scanlM #-}-scanlM :: Monad m => (b -> a -> m b) -> b -> Stream m a -> Stream m b-scanlM fstep begin s = begin `cons` postscanlM fstep begin s--{-# INLINE scanl #-}-scanl :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b-scanl f = scanlM (\a b -> return (f a b))--{-# INLINE_NORMAL scanl1M #-}-scanl1M :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a-scanl1M fstep (Stream step state) = Stream step' (state, Nothing)- where- {-# INLINE_LATE step' #-}- step' gst (st, Nothing) = do- r <- step gst st- case r of- Yield x s -> return $ Yield x (s, Just x)- Skip s -> return $ Skip (s, Nothing)- Stop -> return Stop-- step' gst (st, Just acc) = do- r <- step gst st- case r of- Yield y s -> do- z <- fstep acc y- return $ Yield z (s, Just z)- Skip s -> return $ Skip (s, Just acc)- Stop -> return Stop--{-# INLINE scanl1 #-}-scanl1 :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a-scanl1 f = scanl1M (\x y -> return (f x y))--{-# INLINE_NORMAL scanl1M' #-}-scanl1M' :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a-scanl1M' fstep (Stream step state) = Stream step' (state, Nothing)- where- {-# INLINE_LATE step' #-}- step' gst (st, Nothing) = do- r <- step gst st- case r of- Yield x s -> x `seq` return $ Yield x (s, Just x)- Skip s -> return $ Skip (s, Nothing)- Stop -> return Stop-- step' gst (st, Just acc) = acc `seq` do- r <- step gst st- case r of- Yield y s -> do- z <- fstep acc y- z `seq` return $ Yield z (s, Just z)- Skip s -> return $ Skip (s, Just acc)- Stop -> return Stop--{-# INLINE scanl1' #-}-scanl1' :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a-scanl1' f = scanl1M' (\x y -> return (f x y))--{-# INLINE tap #-}-tap :: Monad m => Fold m a b -> Stream m a -> Stream m a-tap (Fold fstep initial extract) (Stream step state) = Stream step' Nothing-- where-- step' _ Nothing = do- r <- initial- return $ Skip (Just (r, state))-- step' gst (Just (acc, st)) = do- r <- step gst st- case r of- Yield x s -> do- acc' <- fstep acc x- return $ Yield x (Just (acc', s))- Skip s -> return $ Skip (Just (acc, s))- Stop -> do- void $ extract acc- return $ Stop------------------------------------------------------------------------------------ Filtering----------------------------------------------------------------------------------{-# INLINE_NORMAL takeWhileM #-}-takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a-takeWhileM f (Stream step state) = Stream step' state- where- {-# INLINE_LATE step' #-}- step' gst st = do- r <- step gst st- case r of- Yield x s -> do- b <- f x- return $ if b then Yield x s else Stop- Skip s -> return $ Skip s- Stop -> return Stop--{-# INLINE takeWhile #-}-takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-takeWhile f = takeWhileM (return . f)--{-# INLINE_NORMAL drop #-}-drop :: Monad m => Int -> Stream m a -> Stream m a-drop n (Stream step state) = Stream step' (state, Just n)- where- {-# INLINE_LATE step' #-}- step' gst (st, Just i)- | i > 0 = do- r <- step gst st- return $- case r of- Yield _ s -> Skip (s, Just (i - 1))- Skip s -> Skip (s, Just i)- Stop -> Stop- | otherwise = return $ Skip (st, Nothing)-- step' gst (st, Nothing) = do- r <- step gst st- return $- case r of- Yield x s -> Yield x (s, Nothing)- Skip s -> Skip (s, Nothing)- Stop -> Stop--data DropWhileState s a- = DropWhileDrop s- | DropWhileYield a s- | DropWhileNext s--{-# INLINE_NORMAL dropWhileM #-}-dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a-dropWhileM f (Stream step state) = Stream step' (DropWhileDrop state)- where- {-# INLINE_LATE step' #-}- step' gst (DropWhileDrop st) = do- r <- step gst st- case r of- Yield x s -> do- b <- f x- if b- then return $ Skip (DropWhileDrop s)- else return $ Skip (DropWhileYield x s)- Skip s -> return $ Skip (DropWhileDrop s)- Stop -> return Stop-- step' gst (DropWhileNext st) = do- r <- step gst st- case r of- Yield x s -> return $ Skip (DropWhileYield x s)- Skip s -> return $ Skip (DropWhileNext s)- Stop -> return Stop-- step' _ (DropWhileYield x st) = return $ Yield x (DropWhileNext st)--{-# INLINE dropWhile #-}-dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-dropWhile f = dropWhileM (return . f)--{-# INLINE_NORMAL filterM #-}-filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a-filterM f (Stream step state) = Stream step' state- where- {-# INLINE_LATE step' #-}- step' gst st = do- r <- step gst st- case r of- Yield x s -> do- b <- f x- return $ if b- then Yield x s- else Skip s- Skip s -> return $ Skip s- Stop -> return Stop--{-# INLINE filter #-}-filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-filter f = filterM (return . f)--{-# INLINE_NORMAL uniq #-}-uniq :: (Eq a, Monad m) => Stream m a -> Stream m a-uniq (Stream step state) = Stream step' (Nothing, state)- where- {-# INLINE_LATE step' #-}- step' gst (Nothing, st) = do- r <- step gst st- case r of- Yield x s -> return $ Yield x (Just x, s)- Skip s -> return $ Skip (Nothing, s)- Stop -> return Stop- step' gst (Just x, st) = do- r <- step gst st- case r of- Yield y s | x == y -> return $ Skip (Just x, s)- | otherwise -> return $ Yield y (Just y, s)- Skip s -> return $ Skip (Just x, s)- Stop -> return Stop----------------------------------------------------------------------------------- Transformation by Mapping---------------------------------------------------------------------------------{-# INLINE_NORMAL sequence #-}-sequence :: Monad m => Stream m (m a) -> Stream m a-sequence (Stream step state) = Stream step' state- where- {-# INLINE_LATE step' #-}- step' gst st = do- r <- step (adaptState gst) st- case r of- Yield x s -> x >>= \a -> return (Yield a s)- Skip s -> return $ Skip s- Stop -> return Stop----------------------------------------------------------------------------------- Inserting---------------------------------------------------------------------------------data LoopState x s = FirstYield s- | InterspersingYield s- | YieldAndCarry x s--{-# INLINE_NORMAL intersperseM #-}-intersperseM :: Monad m => m a -> Stream m a -> Stream m a-intersperseM m (Stream step state) = Stream step' (FirstYield state)- where- {-# INLINE_LATE step' #-}- step' gst (FirstYield st) = do- r <- step gst st- return $- case r of- Yield x s -> Skip (YieldAndCarry x s)- Skip s -> Skip (FirstYield s)- Stop -> Stop-- step' gst (InterspersingYield st) = do- r <- step gst st- case r of- Yield x s -> do- a <- m- return $ Yield a (YieldAndCarry x s)- Skip s -> return $ Skip $ InterspersingYield s- Stop -> return Stop-- step' _ (YieldAndCarry x st) = return $ Yield x (InterspersingYield st)--data SuffixState s a- = SuffixElem s- | SuffixSuffix s- | SuffixYield a (SuffixState s a)--{-# INLINE_NORMAL intersperseSuffix #-}-intersperseSuffix :: forall m a. Monad m => m a -> Stream m a -> Stream m a-intersperseSuffix action (Stream step state) = Stream step' (SuffixElem state)- where- {-# INLINE_LATE step' #-}- step' gst (SuffixElem st) = do- r <- step gst st- return $ case r of- Yield x s -> Skip (SuffixYield x (SuffixSuffix s))- Skip s -> Skip (SuffixElem s)- Stop -> Stop-- step' _ (SuffixSuffix st) = do- action >>= \r -> return $ Skip (SuffixYield r (SuffixElem st))-- step' _ (SuffixYield x next) = return $ Yield x next--{-# INLINE intersperse #-}-intersperse :: Monad m => a -> Stream m a -> Stream m a-intersperse a = intersperseM (return a)--{-# INLINE_NORMAL insertBy #-}-insertBy :: Monad m => (a -> a -> Ordering) -> a -> Stream m a -> Stream m a-insertBy cmp a (Stream step state) = Stream step' (state, False, Nothing)- where- {-# INLINE_LATE step' #-}- step' gst (st, False, _) = do- r <- step gst st- case r of- Yield x s -> case cmp a x of- GT -> return $ Yield x (s, False, Nothing)- _ -> return $ Yield a (s, True, Just x)- Skip s -> return $ Skip (s, False, Nothing)- Stop -> return $ Yield a (st, True, Nothing)-- step' _ (_, True, Nothing) = return Stop-- step' gst (st, True, Just prev) = do- r <- step gst st- case r of- Yield x s -> return $ Yield prev (s, True, Just x)- Skip s -> return $ Skip (s, True, Just prev)- Stop -> return $ Yield prev (st, True, Nothing)----------------------------------------------------------------------------------- Deleting---------------------------------------------------------------------------------{-# INLINE_NORMAL deleteBy #-}-deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a-deleteBy eq x (Stream step state) = Stream step' (state, False)- where- {-# INLINE_LATE step' #-}- step' gst (st, False) = do- r <- step gst st- case r of- Yield y s -> return $- if eq x y then Skip (s, True) else Yield y (s, False)- Skip s -> return $ Skip (s, False)- Stop -> return Stop-- step' gst (st, True) = do- r <- step gst st- case r of- Yield y s -> return $ Yield y (s, True)- Skip s -> return $ Skip (s, True)- Stop -> return Stop----------------------------------------------------------------------------------- Transformation by Map and Filter----------------------------------------------------------------------------------- XXX Will this always fuse properly?-{-# INLINE_NORMAL mapMaybe #-}-mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b-mapMaybe f = fmap fromJust . filter isJust . map f--{-# INLINE_NORMAL mapMaybeM #-}-mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b-mapMaybeM f = fmap fromJust . filter isJust . mapM f----------------------------------------------------------------------------------- Zipping---------------------------------------------------------------------------------{-# INLINE_NORMAL indexed #-}-indexed :: Monad m => Stream m a -> Stream m (Int, a)-indexed (Stream step state) = Stream step' (state, 0)- where- {-# INLINE_LATE step' #-}- step' gst (st, i) = i `seq` do- r <- step (adaptState gst) st- case r of- Yield x s -> return $ Yield (i, x) (s, i+1)- Skip s -> return $ Skip (s, i)- Stop -> return Stop--{-# INLINE_NORMAL indexedR #-}-indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)-indexedR m (Stream step state) = Stream step' (state, m)- where- {-# INLINE_LATE step' #-}- step' gst (st, i) = i `seq` do- r <- step (adaptState gst) st- case r of- Yield x s -> let i' = i - 1- in return $ Yield (i, x) (s, i')- Skip s -> return $ Skip (s, i)- Stop -> return Stop--{-# INLINE_NORMAL zipWithM #-}-zipWithM :: Monad m- => (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c-zipWithM f (Stream stepa ta) (Stream stepb tb) = Stream step (ta, tb, Nothing)- where- {-# INLINE_LATE step #-}- step gst (sa, sb, Nothing) = do- r <- stepa (adaptState gst) sa- return $- case r of- Yield x sa' -> Skip (sa', sb, Just x)- Skip sa' -> Skip (sa', sb, Nothing)- Stop -> Stop-- step gst (sa, sb, Just x) = do- r <- stepb (adaptState gst) sb- case r of- Yield y sb' -> do- z <- f x y- return $ Yield z (sa, sb', Nothing)- Skip sb' -> return $ Skip (sa, sb', Just x)- Stop -> return Stop--#if __GLASGOW_HASKELL__ >= 801-{-# RULES "zipWithM xs xs"- forall f xs. zipWithM @Identity f xs xs = mapM (\x -> f x x) xs #-}-#endif--{-# INLINE zipWith #-}-zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c-zipWith f = zipWithM (\a b -> return (f a b))----------------------------------------------------------------------------------- Merging---------------------------------------------------------------------------------{-# INLINE_NORMAL mergeByM #-}-mergeByM- :: (Monad m)- => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeByM cmp (Stream stepa ta) (Stream stepb tb) =- Stream step (Just ta, Just tb, Nothing, Nothing)- where- {-# INLINE_LATE step #-}-- -- one of the values is missing, and the corresponding stream is running- step gst (Just sa, sb, Nothing, b) = do- r <- stepa gst sa- return $ case r of- Yield a sa' -> Skip (Just sa', sb, Just a, b)- Skip sa' -> Skip (Just sa', sb, Nothing, b)- Stop -> Skip (Nothing, sb, Nothing, b)-- step gst (sa, Just sb, a, Nothing) = do- r <- stepb gst sb- return $ case r of- Yield b sb' -> Skip (sa, Just sb', a, Just b)- Skip sb' -> Skip (sa, Just sb', a, Nothing)- Stop -> Skip (sa, Nothing, a, Nothing)-- -- both the values are available- step _ (sa, sb, Just a, Just b) = do- res <- cmp a b- return $ case res of- GT -> Yield b (sa, sb, Just a, Nothing)- _ -> Yield a (sa, sb, Nothing, Just b)-- -- one of the values is missing, corresponding stream is done- step _ (Nothing, sb, Nothing, Just b) =- return $ Yield b (Nothing, sb, Nothing, Nothing)-- step _ (sa, Nothing, Just a, Nothing) =- return $ Yield a (sa, Nothing, Nothing, Nothing)-- step _ (Nothing, Nothing, Nothing, Nothing) = return Stop--{-# INLINE mergeBy #-}-mergeBy- :: (Monad m)- => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeBy cmp = mergeByM (\a b -> return $ cmp a b)----------------------------------------------------------------------------------- Transformation comprehensions---------------------------------------------------------------------------------{-# INLINE_NORMAL the #-}-the :: (Eq a, Monad m) => Stream m a -> m (Maybe a)-the (Stream step state) = go state- where- go st = do- r <- step defState st- case r of- Yield x s -> go' x s- Skip s -> go s- Stop -> return Nothing- go' n st = do- r <- step defState st- case r of- Yield x s | x == n -> go' n s- | otherwise -> return Nothing- Skip s -> go' n s- Stop -> return (Just n)------------------------------------------------------------------------------------- UTF8 Encoding / Decoding------------------------------------------------------------------------------------- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.--{-# INLINE ord2 #-}-ord2 :: Char -> WList-ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 6) + 0xC0- x2 = fromIntegral $ (n .&. 0x3F) + 0x80--{-# INLINE ord3 #-}-ord3 :: Char -> WList-ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 12) + 0xE0- x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80- x3 = fromIntegral $ (n .&. 0x3F) + 0x80--{-# INLINE ord4 #-}-ord4 :: Char -> WList-ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 18) + 0xF0- x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80- x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80- x4 = fromIntegral $ (n .&. 0x3F) + 0x80--data CodingFailureMode- = TransliterateCodingFailure- | ErrorOnCodingFailure- deriving (Show)--{-# INLINE replacementChar #-}-replacementChar :: Char-replacementChar = '\xFFFD'---- Int helps in cheaper conversion from Int to Char-type CodePoint = Int-type DecodeState = Word8---- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.--{-# INLINE runFold #-}-runFold :: (Monad m) => Fold m a b -> Stream m a -> m b-runFold (Fold step begin done) = foldlMx' step begin done---- XXX Use names decodeSuccess = 0, decodeFailure = 12--decodeTable :: [Word8]-decodeTable = [- -- The first part of the table maps bytes to character classes that- -- to reduce the size of the transition table and create bitmasks.- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,- 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,- 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,- 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,-- -- The second part is a transition table that maps a combination- -- of a state of the automaton and a character class to a state.- 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,- 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,- 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,- 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,- 12,36,12,12,12,12,12,12,12,12,12,12- ]--utf8d :: A.Array Word8-utf8d =- unsafePerformIO- -- Aligning to cacheline makes a barely noticeable difference- -- XXX currently alignment is not implemented for unmanaged allocation- $ runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))- (fromList decodeTable)---- | Return element at the specified index without checking the bounds.--- and without touching the foreign ptr.-{-# INLINE_NORMAL unsafePeekElemOff #-}-unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a-unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x---- decode is split into two separate cases to avoid branching instructions.--- From the higher level flow we already know which case we are in so we can--- call the appropriate decode function.------ When the state is 0-{-# INLINE decode0 #-}-decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint-decode0 table byte =- let !t = table `unsafePeekElemOff` fromIntegral byte- !codep' = (0xff `shiftR` (fromIntegral t)) .&. fromIntegral byte- !state' = table `unsafePeekElemOff` (256 + fromIntegral t)- in assert ((byte > 0x7f || error showByte)- && (state' /= 0 || error (showByte ++ showTable)))- (Tuple' state' codep')-- where-- utf8table =- let !(Ptr addr) = table- end = table `plusPtr` 364- in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8- showByte = "Streamly: decode0: byte: " ++ show byte- showTable = " table: " ++ show utf8table---- When the state is not 0-{-# INLINE decode1 #-}-decode1- :: Ptr Word8- -> DecodeState- -> CodePoint- -> Word8- -> Tuple' DecodeState CodePoint-decode1 table state codep byte =- -- Remember codep is Int type!- -- Can it be unsafe to convert the resulting Int to Char?- let !t = table `unsafePeekElemOff` fromIntegral byte- !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)- !state' = table `unsafePeekElemOff`- (256 + fromIntegral state + fromIntegral t)- in assert (codep' <= 0x10FFFF- || error (showByte ++ showState state codep))- (Tuple' state' codep')- where-- utf8table =- let !(Ptr addr) = table- end = table `plusPtr` 364- in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8- showByte = "Streamly: decode1: byte: " ++ show byte- showState st cp =- " state: " ++ show st ++- " codepoint: " ++ show cp ++- " table: " ++ show utf8table---- We can divide the errors in three general categories:--- * A non-starter was encountered in a begin state--- * A starter was encountered without completing a codepoint--- * The last codepoint was not complete (input underflow)----data DecodeError = DecodeError !DecodeState !CodePoint deriving Show--data FreshPoint s a- = FreshPointDecodeInit s- | FreshPointDecodeInit1 s Word8- | FreshPointDecodeFirst s Word8- | FreshPointDecoding s !DecodeState !CodePoint- | YieldAndContinue a (FreshPoint s a)- | Done---- XXX Add proper error messages--- XXX Implement this in terms of decodeUtf8Either-{-# INLINE_NORMAL decodeUtf8With #-}-decodeUtf8With :: Monad m => CodingFailureMode -> Stream m Word8 -> Stream m Char-decodeUtf8With cfm (Stream step state) =- let Array p _ _ = utf8d- !ptr = (unsafeForeignPtrToPtr p)- in Stream (step' ptr) (FreshPointDecodeInit state)- where- {-# INLINE transliterateOrError #-}- transliterateOrError e s =- case cfm of- ErrorOnCodingFailure -> error e- TransliterateCodingFailure -> YieldAndContinue replacementChar s- {-# INLINE inputUnderflow #-}- inputUnderflow =- case cfm of- ErrorOnCodingFailure ->- error "Streamly.Streams.StreamD.decodeUtf8With: Input Underflow"- TransliterateCodingFailure -> YieldAndContinue replacementChar Done- {-# INLINE_LATE step' #-}- step' _ gst (FreshPointDecodeInit st) = do- r <- step (adaptState gst) st- return $ case r of- Yield x s -> Skip (FreshPointDecodeInit1 s x)- Skip s -> Skip (FreshPointDecodeInit s)- Stop -> Skip Done-- step' _ _ (FreshPointDecodeInit1 st x) = do- -- Note: It is important to use a ">" instead of a "<=" test- -- here for GHC to generate code layout for default branch- -- prediction for the common case. This is fragile and might- -- change with the compiler versions, we need a more reliable- -- "likely" primitive to control branch predication.- case x > 0x7f of- False ->- return $ Skip $ YieldAndContinue- (unsafeChr (fromIntegral x))- (FreshPointDecodeInit st)- -- Using a separate state here generates a jump to a- -- separate code block in the core which seems to perform- -- slightly better for the non-ascii case.- True -> return $ Skip $ FreshPointDecodeFirst st x-- -- XXX should we merge it with FreshPointDecodeInit1?- step' table _ (FreshPointDecodeFirst st x) = do- let (Tuple' sv cp) = decode0 table x- return $- case sv of- 12 ->- Skip $- transliterateOrError- "Streamly.Streams.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"- (FreshPointDecodeInit st)- 0 -> error "unreachable state"- _ -> Skip (FreshPointDecoding st sv cp)-- -- We recover by trying the new byte x a starter of a new codepoint.- -- XXX need to use the same recovery in array decoding routine as well- step' table gst (FreshPointDecoding st statePtr codepointPtr) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- let (Tuple' sv cp) = decode1 table statePtr codepointPtr x- return $- case sv of- 0 -> Skip $ YieldAndContinue (unsafeChr cp)- (FreshPointDecodeInit s)- 12 ->- Skip $- transliterateOrError- "Streamly.Streams.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"- (FreshPointDecodeInit1 s x)- _ -> Skip (FreshPointDecoding s sv cp)- Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)- Stop -> return $ Skip inputUnderflow-- step' _ _ (YieldAndContinue c s) = return $ Yield c s- step' _ _ Done = return Stop--{-# INLINE decodeUtf8 #-}-decodeUtf8 :: Monad m => Stream m Word8 -> Stream m Char-decodeUtf8 = decodeUtf8With ErrorOnCodingFailure--{-# INLINE decodeUtf8Lenient #-}-decodeUtf8Lenient :: Monad m => Stream m Word8 -> Stream m Char-decodeUtf8Lenient = decodeUtf8With TransliterateCodingFailure--{-# INLINE_NORMAL resumeDecodeUtf8Either #-}-resumeDecodeUtf8Either- :: Monad m- => DecodeState- -> CodePoint- -> Stream m Word8- -> Stream m (Either DecodeError Char)-resumeDecodeUtf8Either dst codep (Stream step state) =- let Array p _ _ = utf8d- !ptr = (unsafeForeignPtrToPtr p)- stt =- if dst == 0- then FreshPointDecodeInit state- else FreshPointDecoding state dst codep- in Stream (step' ptr) stt- where- {-# INLINE_LATE step' #-}- step' _ gst (FreshPointDecodeInit st) = do- r <- step (adaptState gst) st- return $ case r of- Yield x s -> Skip (FreshPointDecodeInit1 s x)- Skip s -> Skip (FreshPointDecodeInit s)- Stop -> Skip Done-- step' _ _ (FreshPointDecodeInit1 st x) = do- -- Note: It is important to use a ">" instead of a "<=" test- -- here for GHC to generate code layout for default branch- -- prediction for the common case. This is fragile and might- -- change with the compiler versions, we need a more reliable- -- "likely" primitive to control branch predication.- case x > 0x7f of- False ->- return $ Skip $ YieldAndContinue- (Right $ unsafeChr (fromIntegral x))- (FreshPointDecodeInit st)- -- Using a separate state here generates a jump to a- -- separate code block in the core which seems to perform- -- slightly better for the non-ascii case.- True -> return $ Skip $ FreshPointDecodeFirst st x-- -- XXX should we merge it with FreshPointDecodeInit1?- step' table _ (FreshPointDecodeFirst st x) = do- let (Tuple' sv cp) = decode0 table x- return $- case sv of- 12 ->- Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))- (FreshPointDecodeInit st)- 0 -> error "unreachable state"- _ -> Skip (FreshPointDecoding st sv cp)-- -- We recover by trying the new byte x a starter of a new codepoint.- -- XXX need to use the same recovery in array decoding routine as well- step' table gst (FreshPointDecoding st statePtr codepointPtr) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- let (Tuple' sv cp) = decode1 table statePtr codepointPtr x- return $- case sv of- 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)- (FreshPointDecodeInit s)- 12 ->- Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)- (FreshPointDecodeInit1 s x)- _ -> Skip (FreshPointDecoding s sv cp)- Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)- Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done-- step' _ _ (YieldAndContinue c s) = return $ Yield c s- step' _ _ Done = return Stop--{-# INLINE_NORMAL decodeUtf8Either #-}-decodeUtf8Either :: Monad m- => Stream m Word8 -> Stream m (Either DecodeError Char)-decodeUtf8Either = resumeDecodeUtf8Either 0 0--data FlattenState s a- = OuterLoop s !(Maybe (DecodeState, CodePoint))- | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)- | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8- | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)- !DecodeState !CodePoint- | YAndC !Char (FlattenState s a) -- These constructors can be- -- encoded in the FreshPoint- -- type, I prefer to keep these- -- flat even though that means- -- coming up with new names- | D---- The normal decodeUtf8 above should fuse with flattenArrays--- to create this exact code but it doesn't for some reason, as of now this--- remains the fastest way I could figure out to decodeUtf8.------ XXX Add Proper error messages-{-# INLINE_NORMAL decodeUtf8ArraysWith #-}-decodeUtf8ArraysWith ::- MonadIO m- => CodingFailureMode- -> Stream m (A.Array Word8)- -> Stream m Char-decodeUtf8ArraysWith cfm (Stream step state) =- let Array p _ _ = utf8d- !ptr = (unsafeForeignPtrToPtr p)- in Stream (step' ptr) (OuterLoop state Nothing)- where- {-# INLINE transliterateOrError #-}- transliterateOrError e s =- case cfm of- ErrorOnCodingFailure -> error e- TransliterateCodingFailure -> YAndC replacementChar s- {-# INLINE inputUnderflow #-}- inputUnderflow =- case cfm of- ErrorOnCodingFailure ->- error- "Streamly.Streams.StreamD.decodeUtf8ArraysWith: Input Underflow"- TransliterateCodingFailure -> YAndC replacementChar D- {-# INLINE_LATE step' #-}- step' _ gst (OuterLoop st Nothing) = do- r <- step (adaptState gst) st- return $- case r of- Yield A.Array {..} s ->- let p = unsafeForeignPtrToPtr aStart- in Skip (InnerLoopDecodeInit s aStart p aEnd)- Skip s -> Skip (OuterLoop s Nothing)- Stop -> Skip D- step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do- r <- step (adaptState gst) st- return $- case r of- Yield A.Array {..} s ->- let p = unsafeForeignPtrToPtr aStart- in Skip (InnerLoopDecoding s aStart p aEnd ds cp)- Skip s -> Skip (OuterLoop s dst)- Stop -> Skip inputUnderflow- step' _ _ (InnerLoopDecodeInit st startf p end)- | p == end = do- liftIO $ touchForeignPtr startf- return $ Skip $ OuterLoop st Nothing- step' _ _ (InnerLoopDecodeInit st startf p end) = do- x <- liftIO $ peek p- -- Note: It is important to use a ">" instead of a "<=" test here for- -- GHC to generate code layout for default branch prediction for the- -- common case. This is fragile and might change with the compiler- -- versions, we need a more reliable "likely" primitive to control- -- branch predication.- case x > 0x7f of- False ->- return $ Skip $ YAndC- (unsafeChr (fromIntegral x))- (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)- -- Using a separate state here generates a jump to a separate code- -- block in the core which seems to perform slightly better for the- -- non-ascii case.- True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x-- step' table _ (InnerLoopDecodeFirst st startf p end x) = do- let (Tuple' sv cp) = decode0 table x- return $- case sv of- 12 ->- Skip $- transliterateOrError- "Streamly.Streams.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"- (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)- 0 -> error "unreachable state"- _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)- step' _ _ (InnerLoopDecoding st startf p end sv cp)- | p == end = do- liftIO $ touchForeignPtr startf- return $ Skip $ OuterLoop st (Just (sv, cp))- step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do- x <- liftIO $ peek p- let (Tuple' sv cp) = decode1 table statePtr codepointPtr x- return $- case sv of- 0 ->- Skip $- YAndC- (unsafeChr cp)- (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)- 12 ->- Skip $- transliterateOrError- "Streamly.Streams.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"- (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)- _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)- step' _ _ (YAndC c s) = return $ Yield c s- step' _ _ D = return Stop--{-# INLINE decodeUtf8Arrays #-}-decodeUtf8Arrays ::- MonadIO m- => Stream m (A.Array Word8)- -> Stream m Char-decodeUtf8Arrays = decodeUtf8ArraysWith ErrorOnCodingFailure--{-# INLINE decodeUtf8ArraysLenient #-}-decodeUtf8ArraysLenient ::- MonadIO m- => Stream m (A.Array Word8)- -> Stream m Char-decodeUtf8ArraysLenient = decodeUtf8ArraysWith TransliterateCodingFailure--data WList = WCons !Word8 !WList | WNil--data EncodeState s = EncodeState s !WList---- More yield points improve performance, but I am not sure if they can cause--- too much code bloat or some trouble with fusion. So keeping only two yield--- points for now, one for the ascii chars (fast path) and one for all other--- paths (slow path).-{-# INLINE_NORMAL encodeUtf8 #-}-encodeUtf8 :: Monad m => Stream m Char -> Stream m Word8-encodeUtf8 (Stream step state) = Stream step' (EncodeState state WNil)- where- {-# INLINE_LATE step' #-}- step' gst (EncodeState st WNil) = do- r <- step (adaptState gst) st- return $- case r of- Yield c s ->- case ord c of- x- | x <= 0x7F ->- Yield (fromIntegral x) (EncodeState s WNil)- | x <= 0x7FF -> Skip (EncodeState s (ord2 c))- | x <= 0xFFFF ->- if isSurrogate c- then error- "Streamly.Streams.StreamD.encodeUtf8: Encountered a surrogate"- else Skip (EncodeState s (ord3 c))- | otherwise -> Skip (EncodeState s (ord4 c))- Skip s -> Skip (EncodeState s WNil)- Stop -> Stop- step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
− src/Streamly/Streams/StreamDK.hs
@@ -1,165 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RecordWildCards #-}--- {-# LANGUAGE ScopedTypeVariables #-}--#include "inline.hs"---- |--- Module : Streamly.Streams.StreamDK--- Copyright : (c) 2019 Composewell Technologies--- License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-----module Streamly.Streams.StreamDK- (- -- * Stream Type-- Stream- , Step (..)-- -- * Construction- , nil- , cons- , consM- , unfoldr- , unfoldrM- , replicateM-- -- * Folding- , uncons- , foldrS-- -- * Specific Folds- , drain- )-where--import Streamly.Streams.StreamDK.Type (Stream(..), Step(..))------------------------------------------------------------------------------------ Construction----------------------------------------------------------------------------------nil :: Monad m => Stream m a-nil = Stream $ return Stop--{-# INLINE_NORMAL cons #-}-cons :: Monad m => a -> Stream m a -> Stream m a-cons x xs = Stream $ return $ Yield x xs--consM :: Monad m => m a -> Stream m a -> Stream m a-consM eff xs = Stream $ eff >>= \x -> return $ Yield x xs--unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a-unfoldrM next state = Stream (step' state)- where- step' st = do- r <- next st- return $ case r of- Just (x, s) -> Yield x (Stream (step' s))- Nothing -> Stop-{--unfoldrM next s0 = buildM $ \yld stp ->- let go s = do- r <- next s- case r of- Just (a, b) -> yld a (go b)- Nothing -> stp- in go s0--}--{-# INLINE unfoldr #-}-unfoldr :: Monad m => (b -> Maybe (a, b)) -> b -> Stream m a-unfoldr next s0 = build $ \yld stp ->- let go s =- case next s of- Just (a, b) -> yld a (go b)- Nothing -> stp- in go s0--replicateM :: Monad m => Int -> a -> Stream m a-replicateM n x = Stream (step n)- where- step i = return $- if i <= 0- then Stop- else Yield x (Stream (step (i - 1)))------------------------------------------------------------------------------------ Folding----------------------------------------------------------------------------------uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))-uncons (Stream step) = do- r <- step- return $ case r of- Yield x xs -> Just (x, xs)- Stop -> Nothing---- | Lazy right associative fold to a stream.-{-# INLINE_NORMAL foldrS #-}-foldrS :: Monad m- => (a -> Stream m b -> Stream m b)- -> Stream m b- -> Stream m a- -> Stream m b-foldrS f streamb = go- where- go (Stream stepa) = Stream $ do- r <- stepa- case r of- Yield x xs -> let Stream step = f x (go xs) in step- Stop -> let Stream step = streamb in step--{-# INLINE_LATE foldrM #-}-foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b-foldrM fstep acc ys = go ys- where- go (Stream step) = do- r <- step- case r of- Yield x xs -> fstep x (go xs)- Stop -> acc--{-# INLINE_NORMAL build #-}-build :: Monad m- => forall a. (forall b. (a -> b -> b) -> b -> b) -> Stream m a-build g = g cons nil--{-# RULES-"foldrM/build" forall k z (g :: forall b. (a -> b -> b) -> b -> b).- foldrM k z (build g) = g k z #-}--{---- To fuse foldrM with unfoldrM we need the type m1 to be polymorphic such that--- it is either Monad m or Stream m. So that we can use cons/nil as well as--- monadic construction function as its arguments.----{-# INLINE_NORMAL buildM #-}-buildM :: Monad m- => forall a. (forall b. (a -> m1 b -> m1 b) -> m1 b -> m1 b) -> Stream m a-buildM g = g cons nil--}------------------------------------------------------------------------------------ Specific folds----------------------------------------------------------------------------------{-# INLINE drain #-}-drain :: Monad m => Stream m a -> m ()-drain = foldrM (\_ xs -> xs) (return ())-{--drain (Stream step) = do- r <- step- case r of- Yield _ next -> drain next- Stop -> return ()- -}
− src/Streamly/Streams/StreamDK/Type.hs
@@ -1,108 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts #-}---- |--- Module : Streamly.StreamDK.Type--- Copyright : (c) 2019 Composewell Technologies--- License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ A CPS style stream using a constructor based representation instead of a--- function based representation.------ Streamly internally uses two fundamental stream representations, (1) streams--- with an open or arbitrary control flow (we call it StreamK), (2) streams--- with a structured or closed loop control flow (we call it StreamD). The--- higher level stream types can use any of these representations under the--- hood and can interconvert between the two.------ StreamD:------ StreamD is a non-recursive data type in which the state of the stream and--- the step function are separate. When the step function is called, a stream--- element and the new stream state is yielded. The generated element and the--- state are passed to the next consumer in the loop. The state is threaded--- around in the loop until control returns back to the original step function--- to run the next step. This creates a structured closed loop representation--- (like "for" loops in C) with state of each step being hidden/abstracted or--- existential within that step. This creates a loop representation identical--- to the "for" or "while" loop constructs in imperative languages, the states--- of the steps combined together constitute the state of the loop iteration.------ Internally most combinators use a closed loop representation because it--- provides very high efficiency due to stream fusion. The performance of this--- representation is competitive to the C language implementations.------ Pros and Cons of StreamD:------ 1) stream-fusion: This representation can be optimized very efficiently by--- the compiler because the state is explicitly separated from step functions,--- represented using pure data constructors and visible to the compiler, the--- stream steps can be fused using case-of-case transformations and the state--- can be specialized using spec-constructor optimization, yielding a C like--- tight loop/state machine with no constructors, the state is used unboxed and--- therefore no unnecessary allocation.------ 2) Because of a closed representation consing too many elements in this type--- of stream does not scale, it will have quadratic performance slowdown. Each--- cons creates a layer that needs to return the control back to the caller.--- Another implementation of cons is possible but that will have to box/unbox--- the state and will not fuse. So effectively cons breaks fusion.------ 3) unconsing an item from the stream breaks fusion, we have to "pause" the--- loop, rebox and save the state.------ 3) Exception handling is easy to implement in this model because control--- flow is structured in the loop and cannot be arbitrary. Therefore,--- implementing "bracket" is natural.------ 4) Round-robin scheduling for co-operative multitasking is easy to implement.------ 5) It fuses well with the direct style Fold implementation.------ StreamK/StreamDK:------ StreamDK i.e. the stream defined in this module, like StreamK, is a--- recursive data type which has no explicit state defined using constructors,--- each step yields an element and a computation representing the rest of the--- stream. Stream state is part of the function representing the rest of the--- stream. This creates an open computation representation, or essentially a--- continuation passing style computation. After the stream step is executed,--- the caller is free to consume the produced element and then send the control--- wherever it wants, there is no restriction on the control to return back--- somewhere, the control is free to go anywhere. The caller may decide not to--- consume the rest of the stream. This representation is more like a "goto"--- based implementation in imperative languages.------ Pros and Cons of StreamK:------ 1) The way StreamD can be optimized using stream-fusion, this type can be--- optimized using foldr/build fusion. However, foldr/build has not yet been--- fully implemented for StreamK/StreamDK.------ 2) Using cons is natural in this representation, unlike in StreamD it does--- not have a quadratic slowdown. Currently, we in fact wrap StreamD in StreamK--- to support a better cons operation.------ 3) Similarly, uncons is natural in this representation.------ 4) Exception handling is not easy to implement because of the "goto" nature--- of CPS.------ 5) Composable folds are not implemented/proven, however, intuition says that--- a push style CPS representation should be able to be used along with StreamK--- to efficiently implement composable folds.--module Streamly.Streams.StreamDK.Type- ( Step(..)- , Stream (..)- )-where---- XXX Use Cons and Nil instead of Yield and Stop?-data Step m a = Yield a (Stream m a) | Stop--data Stream m a = Stream (m (Step m a))
− src/Streamly/Streams/StreamK.hs
@@ -1,1037 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX--#include "inline.hs"---- |--- Module : Streamly.Streams.StreamK--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--------- Continuation passing style (CPS) stream implementation. The symbol 'K' below--- denotes a function as well as a Kontinuation.------ @--- import qualified Streamly.Streams.StreamK as K--- @----module Streamly.Streams.StreamK- (- -- * A class for streams- IsStream (..)- , adapt-- -- * The stream type- , Stream-- -- * Construction Primitives- , mkStream- , nil- , nilM- , cons- , (.:)-- -- * Elimination Primitives- , foldStream- , foldStreamShared- , foldStreamSVar-- -- * Transformation Primitives- , unShare-- -- * Deconstruction- , uncons-- -- * Generation- -- ** Unfolds- , unfoldr- , unfoldrM-- -- ** Specialized Generation- , repeat- , repeatM- , replicate- , replicateM- , fromIndices- , fromIndicesM-- -- ** Conversions- , yield- , yieldM- , fromFoldable- , fromList- , fromStreamK-- -- * foldr/build- , foldrS- , foldrSM- , buildS- , buildM- , augmentS- , augmentSM-- -- * Elimination- -- ** General Folds- , foldr- , foldr1- , foldrM- , foldrT-- , foldl'- , foldlM'- , foldlS- , foldlT- , foldlx'- , foldlMx'-- -- ** Specialized Folds- , drain- , null- , head- , tail- , init- , elem- , notElem- , all- , any- , last- , minimum- , minimumBy- , maximum- , maximumBy- , findIndices- , lookup- , findM- , find- , (!!)-- -- ** Map and Fold- , mapM_-- -- ** Conversions- , toList- , toStreamK- , hoist-- -- * Transformation- -- ** By folding (scans)- , scanl'- , scanlx'-- -- ** Filtering- , filter- , take- , takeWhile- , drop- , dropWhile-- -- ** Mapping- , map- , mapM- , mapMSerial- , sequence-- -- ** Inserting- , intersperseM- , intersperse- , insertBy-- -- ** Deleting- , deleteBy-- -- ** Reordering- , reverse-- -- ** Map and Filter- , mapMaybe-- -- ** Zipping- , zipWith- , zipWithM-- -- ** Merging- , mergeBy- , mergeByM-- -- ** Nesting- , concatMapBy- , concatMap- , bindWith-- -- ** Transformation comprehensions- , the-- -- * Semigroup Style Composition- , serial-- -- * Utilities- , consMStream- , withLocal-- -- * Deprecated- , Streaming -- deprecated- , once -- deprecated- )-where--import Control.Monad.Trans (MonadTrans(lift))-import Control.Monad (void, join)-import Control.Monad.Reader.Class (MonadReader(..))-import Prelude- hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,- take, filter, all, any, takeWhile, drop, dropWhile, minimum,- maximum, elem, notElem, null, head, tail, init, zipWith, lookup,- foldr1, (!!), replicate, reverse, concatMap)-import qualified Prelude--import Streamly.Internal.Data.SVar-import Streamly.Streams.StreamK.Type------------------------------------------------------------------------------------ Deconstruction----------------------------------------------------------------------------------{-# INLINE uncons #-}-uncons :: (IsStream t, Monad m) => t m a -> m (Maybe (a, t m a))-uncons m =- let stop = return Nothing- single a = return (Just (a, nil))- yieldk a r = return (Just (a, r))- in foldStream defState yieldk single stop m------------------------------------------------------------------------------------ Generation----------------------------------------------------------------------------------{-# INLINE unfoldr #-}-unfoldr :: IsStream t => (b -> Maybe (a, b)) -> b -> t m a-unfoldr next s0 = build $ \yld stp ->- let go s =- case next s of- Just (a, b) -> yld a (go b)- Nothing -> stp- in go s0--{-# INLINE unfoldrM #-}-unfoldrM :: (IsStream t, MonadAsync m) => (b -> m (Maybe (a, b))) -> b -> t m a-unfoldrM step = go- where- go s = sharedM $ \yld _ stp -> do- r <- step s- case r of- Just (a, b) -> yld a (go b)- Nothing -> stp--{---- Generalization of concurrent streams/SVar via unfoldr.------ Unfold a value into monadic actions and then run the resulting monadic--- actions to generate a stream. Since the step of generating the monadic--- action and running them are decoupled we can run the monadic actions--- cooncurrently. For example, the seed could be a list of monadic actions or a--- pure stream of monadic actions.------ We can have different flavors of this depending on the stream type t. The--- concurrent version could be async or ahead etc. Depending on how we queue--- back the feedback portion b, it could be DFS or BFS style.----unfoldrA :: (IsStream t, MonadAsync m) => (b -> Maybe (m a, b)) -> b -> t m a-unfoldrA = undefined--}------------------------------------------------------------------------------------ Special generation------------------------------------------------------------------------------------ | Same as yieldM------ @since 0.2.0-{-# DEPRECATED once "Please use yieldM instead." #-}-{-# INLINE once #-}-once :: (Monad m, IsStream t) => m a -> t m a-once = yieldM---- |--- @--- repeatM = fix . cons--- repeatM = cycle1 . yield--- @------ Generate an infinite stream by repeating a monadic value.------ /Internal/-repeatM :: (IsStream t, MonadAsync m) => m a -> t m a-repeatM = go- where go m = m |: go m---- Generate an infinite stream by repeating a pure value.------ /Internal/-{-# INLINE repeat #-}-repeat :: IsStream t => a -> t m a-repeat a = let x = cons a x in x--{-# INLINE replicateM #-}-replicateM :: (IsStream t, MonadAsync m) => Int -> m a -> t m a-replicateM n m = go n- where- go cnt = if cnt <= 0 then nil else m |: go (cnt - 1)--{-# INLINE replicate #-}-replicate :: IsStream t => Int -> a -> t m a-replicate n a = go n- where- go cnt = if cnt <= 0 then nil else a `cons` go (cnt - 1)--{-# INLINE fromIndicesM #-}-fromIndicesM :: (IsStream t, MonadAsync m) => (Int -> m a) -> t m a-fromIndicesM gen = go 0- where- go i = mkStream $ \st stp sng yld -> do- foldStreamShared st stp sng yld (gen i |: go (i + 1))--{-# INLINE fromIndices #-}-fromIndices :: IsStream t => (Int -> a) -> t m a-fromIndices gen = go 0- where- go n = (gen n) `cons` go (n + 1)------------------------------------------------------------------------------------ Conversions------------------------------------------------------------------------------------ |--- @--- fromFoldable = 'Prelude.foldr' 'cons' 'nil'--- @------ Construct a stream from a 'Foldable' containing pure values:------ @since 0.2.0-{-# INLINE fromFoldable #-}-fromFoldable :: (IsStream t, Foldable f) => f a -> t m a-fromFoldable = Prelude.foldr cons nil--{-# INLINE fromList #-}-fromList :: IsStream t => [a] -> t m a-fromList = fromFoldable--{-# INLINE fromStreamK #-}-fromStreamK :: IsStream t => Stream m a -> t m a-fromStreamK = fromStream------------------------------------------------------------------------------------ Elimination by Folding------------------------------------------------------------------------------------ | Lazy right associative fold.-{-# INLINE foldr #-}-foldr :: (IsStream t, Monad m) => (a -> b -> b) -> b -> t m a -> m b-foldr step acc = foldrM (\x xs -> xs >>= \b -> return (step x b)) (return acc)---- | Right associative fold to an arbitrary transformer monad.-{-# INLINE foldrT #-}-foldrT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)- => (a -> s m b -> s m b) -> s m b -> t m a -> s m b-foldrT step final m = go m- where- go m1 = do- res <- lift $ uncons m1- case res of- Just (h, t) -> step h (go t)- Nothing -> final--{-# INLINE foldr1 #-}-foldr1 :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> m (Maybe a)-foldr1 step m = do- r <- uncons m- case r of- Nothing -> return Nothing- Just (h, t) -> fmap Just (go h t)- where- go p m1 =- let stp = return p- single a = return $ step a p- yieldk a r = fmap (step p) (go a r)- in foldStream defState yieldk single stp m1---- | Strict left fold with an extraction function. Like the standard strict--- left fold, but applies a user supplied extraction function (the third--- argument) to the folded value at the end. This is designed to work with the--- @foldl@ library. The suffix @x@ is a mnemonic for extraction.------ Note that the accumulator is always evaluated including the initial value.-{-# INLINE foldlx' #-}-foldlx' :: forall t m a b x. (IsStream t, Monad m)- => (x -> a -> x) -> x -> (x -> b) -> t m a -> m b-foldlx' step begin done m = get $ go m begin- where- {-# NOINLINE get #-}- get :: t m x -> m b- get m1 =- -- XXX we are not strictly evaluating the accumulator here. Is this- -- okay?- let single = return . done- -- XXX this is foldSingleton. why foldStreamShared?- in foldStreamShared undefined undefined single undefined m1-- -- Note, this can be implemented by making a recursive call to "go",- -- however that is more expensive because of unnecessary recursion- -- that cannot be tail call optimized. Unfolding recursion explicitly via- -- continuations is much more efficient.- go :: t m a -> x -> t m x- go m1 !acc = mkStream $ \_ yld sng _ ->- let stop = sng acc- single a = sng $ step acc a- -- XXX this is foldNonEmptyStream- yieldk a r = foldStream defState yld sng undefined $- go r (step acc a)- in foldStream defState yieldk single stop m1---- | Strict left associative fold.-{-# INLINE foldl' #-}-foldl' :: (IsStream t, Monad m) => (b -> a -> b) -> b -> t m a -> m b-foldl' step begin = foldlx' step begin id---- XXX replace the recursive "go" with explicit continuations.--- | Like 'foldx', but with a monadic step function.-{-# INLINABLE foldlMx' #-}-foldlMx' :: (IsStream t, Monad m)- => (x -> a -> m x) -> m x -> (x -> m b) -> t m a -> m b-foldlMx' step begin done m = go begin m- where- go !acc m1 =- let stop = acc >>= done- single a = acc >>= \b -> step b a >>= done- yieldk a r = acc >>= \b -> step b a >>= \x -> go (return x) r- in foldStream defState yieldk single stop m1---- | Like 'foldl'' but with a monadic step function.-{-# INLINE foldlM' #-}-foldlM' :: (IsStream t, Monad m) => (b -> a -> m b) -> b -> t m a -> m b-foldlM' step begin = foldlMx' step (return begin) return---- | Lazy left fold to a stream.-{-# INLINE foldlS #-}-foldlS :: IsStream t => (t m b -> a -> t m b) -> t m b -> t m a -> t m b-foldlS step begin m = go begin m- where- go acc rest = mkStream $ \st yld sng stp ->- let run x = foldStream st yld sng stp x- stop = run acc- single a = run $ step acc a- yieldk a r = run $ go (step acc a) r- in foldStream (adaptState st) yieldk single stop rest---- | Lazy left fold to an arbitrary transformer monad.-{-# INLINE foldlT #-}-foldlT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)- => (s m b -> a -> s m b) -> s m b -> t m a -> s m b-foldlT step begin m = go begin m- where- go acc m1 = do- res <- lift $ uncons m1- case res of- Just (h, t) -> go (step acc h) t- Nothing -> acc----------------------------------------------------------------------------------- Specialized folds----------------------------------------------------------------------------------- XXX use foldrM to implement folds where possible--- XXX This (commented) definition of drain and mapM_ perform much better on--- some benchmarks but worse on others. Need to investigate why, may there is--- an optimization opportunity that we can exploit.--- drain = foldrM (\_ xs -> return () >> xs) (return ())---- |--- > drain = foldl' (\_ _ -> ()) ()--- > drain = mapM_ (\_ -> return ())-{-# INLINE drain #-}-drain :: (Monad m, IsStream t) => t m a -> m ()-drain = foldrM (\_ xs -> xs) (return ())-{--drain = go- where- go m1 =- let stop = return ()- single _ = return ()- yieldk _ r = go r- in foldStream defState yieldk single stop m1--}--{-# INLINE null #-}-null :: (IsStream t, Monad m) => t m a -> m Bool--- null = foldrM (\_ _ -> return True) (return False)-null m =- let stop = return True- single _ = return False- yieldk _ _ = return False- in foldStream defState yieldk single stop m--{-# INLINE head #-}-head :: (IsStream t, Monad m) => t m a -> m (Maybe a)--- head = foldrM (\x _ -> return $ Just x) (return Nothing)-head m =- let stop = return Nothing- single a = return (Just a)- yieldk a _ = return (Just a)- in foldStream defState yieldk single stop m--{-# INLINE tail #-}-tail :: (IsStream t, Monad m) => t m a -> m (Maybe (t m a))-tail m =- let stop = return Nothing- single _ = return $ Just nil- yieldk _ r = return $ Just r- in foldStream defState yieldk single stop m--{-# INLINE init #-}-init :: (IsStream t, Monad m) => t m a -> m (Maybe (t m a))-init m = go1 m- where- go1 m1 = do- r <- uncons m1- case r of- Nothing -> return Nothing- Just (h, t) -> return . Just $ go h t- go p m1 = mkStream $ \_ yld sng stp ->- let single _ = sng p- yieldk a x = yld p $ go a x- in foldStream defState yieldk single stp m1--{-# INLINE elem #-}-elem :: (IsStream t, Monad m, Eq a) => a -> t m a -> m Bool-elem e m = go m- where- go m1 =- let stop = return False- single a = return (a == e)- yieldk a r = if a == e then return True else go r- in foldStream defState yieldk single stop m1--{-# INLINE notElem #-}-notElem :: (IsStream t, Monad m, Eq a) => a -> t m a -> m Bool-notElem e m = go m- where- go m1 =- let stop = return True- single a = return (a /= e)- yieldk a r = if a == e then return False else go r- in foldStream defState yieldk single stop m1--{-# INLINABLE all #-}-all :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m Bool-all p m = go m- where- go m1 =- let single a | p a = return True- | otherwise = return False- yieldk a r | p a = go r- | otherwise = return False- in foldStream defState yieldk single (return True) m1--{-# INLINABLE any #-}-any :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m Bool-any p m = go m- where- go m1 =- let single a | p a = return True- | otherwise = return False- yieldk a r | p a = return True- | otherwise = go r- in foldStream defState yieldk single (return False) m1---- | Extract the last element of the stream, if any.-{-# INLINE last #-}-last :: (IsStream t, Monad m) => t m a -> m (Maybe a)-last = foldlx' (\_ y -> Just y) Nothing id--{-# INLINE minimum #-}-minimum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)-minimum m = go Nothing m- where- go Nothing m1 =- let stop = return Nothing- single a = return (Just a)- yieldk a r = go (Just a) r- in foldStream defState yieldk single stop m1-- go (Just res) m1 =- let stop = return (Just res)- single a =- if res <= a- then return (Just res)- else return (Just a)- yieldk a r =- if res <= a- then go (Just res) r- else go (Just a) r- in foldStream defState yieldk single stop m1--{-# INLINE minimumBy #-}-minimumBy- :: (IsStream t, Monad m)- => (a -> a -> Ordering) -> t m a -> m (Maybe a)-minimumBy cmp m = go Nothing m- where- go Nothing m1 =- let stop = return Nothing- single a = return (Just a)- yieldk a r = go (Just a) r- in foldStream defState yieldk single stop m1-- go (Just res) m1 =- let stop = return (Just res)- single a = case cmp res a of- GT -> return (Just a)- _ -> return (Just res)- yieldk a r = case cmp res a of- GT -> go (Just a) r- _ -> go (Just res) r- in foldStream defState yieldk single stop m1--{-# INLINE maximum #-}-maximum :: (IsStream t, Monad m, Ord a) => t m a -> m (Maybe a)-maximum m = go Nothing m- where- go Nothing m1 =- let stop = return Nothing- single a = return (Just a)- yieldk a r = go (Just a) r- in foldStream defState yieldk single stop m1-- go (Just res) m1 =- let stop = return (Just res)- single a =- if res <= a- then return (Just a)- else return (Just res)- yieldk a r =- if res <= a- then go (Just a) r- else go (Just res) r- in foldStream defState yieldk single stop m1--{-# INLINE maximumBy #-}-maximumBy :: (IsStream t, Monad m) => (a -> a -> Ordering) -> t m a -> m (Maybe a)-maximumBy cmp m = go Nothing m- where- go Nothing m1 =- let stop = return Nothing- single a = return (Just a)- yieldk a r = go (Just a) r- in foldStream defState yieldk single stop m1-- go (Just res) m1 =- let stop = return (Just res)- single a = case cmp res a of- GT -> return (Just res)- _ -> return (Just a)- yieldk a r = case cmp res a of- GT -> go (Just res) r- _ -> go (Just a) r- in foldStream defState yieldk single stop m1--{-# INLINE (!!) #-}-(!!) :: (IsStream t, Monad m) => t m a -> Int -> m (Maybe a)-m !! i = go i m- where- go n m1 =- let single a | n == 0 = return $ Just a- | otherwise = return Nothing- yieldk a x | n < 0 = return Nothing- | n == 0 = return $ Just a- | otherwise = go (n - 1) x- in foldStream defState yieldk single (return Nothing) m1--{-# INLINE lookup #-}-lookup :: (IsStream t, Monad m, Eq a) => a -> t m (a, b) -> m (Maybe b)-lookup e m = go m- where- go m1 =- let single (a, b) | a == e = return $ Just b- | otherwise = return Nothing- yieldk (a, b) x | a == e = return $ Just b- | otherwise = go x- in foldStream defState yieldk single (return Nothing) m1--{-# INLINE findM #-}-findM :: (IsStream t, Monad m) => (a -> m Bool) -> t m a -> m (Maybe a)-findM p m = go m- where- go m1 =- let single a = do- b <- p a- if b then return $ Just a else return Nothing- yieldk a x = do- b <- p a- if b then return $ Just a else go x- in foldStream defState yieldk single (return Nothing) m1--{-# INLINE find #-}-find :: (IsStream t, Monad m) => (a -> Bool) -> t m a -> m (Maybe a)-find p = findM (return . p)--{-# INLINE findIndices #-}-findIndices :: IsStream t => (a -> Bool) -> t m a -> t m Int-findIndices p = go 0- where- go offset m1 = mkStream $ \st yld sng stp ->- let single a | p a = sng offset- | otherwise = stp- yieldk a x | p a = yld offset $ go (offset + 1) x- | otherwise = foldStream (adaptState st) yld sng stp $- go (offset + 1) x- in foldStream (adaptState st) yieldk single stp m1----------------------------------------------------------------------------------- Map and Fold----------------------------------------------------------------------------------- | Apply a monadic action to each element of the stream and discard the--- output of the action.-{-# INLINE mapM_ #-}-mapM_ :: (IsStream t, Monad m) => (a -> m b) -> t m a -> m ()-mapM_ f m = go m- where- go m1 =- let stop = return ()- single a = void (f a)- yieldk a r = f a >> go r- in foldStream defState yieldk single stop m1----------------------------------------------------------------------------------- Converting folds---------------------------------------------------------------------------------{-# INLINABLE toList #-}-toList :: (IsStream t, Monad m) => t m a -> m [a]-toList = foldr (:) []--{-# INLINE toStreamK #-}-toStreamK :: Stream m a -> Stream m a-toStreamK = id---- Based on suggestions by David Feuer and Pranay Sashank-{-# INLINE hoist #-}-hoist :: (IsStream t, Monad m, Monad n)- => (forall x. m x -> n x) -> t m a -> t n a-hoist f str =- mkStream $ \st yld sng stp ->- let single = return . sng- yieldk a s = return $ yld a (hoist f s)- stop = return stp- state = adaptState st- in join . f $ foldStreamShared state yieldk single stop str------------------------------------------------------------------------------------ Transformation by folding (Scans)----------------------------------------------------------------------------------{-# INLINE scanlx' #-}-scanlx' :: IsStream t => (x -> a -> x) -> x -> (x -> b) -> t m a -> t m b-scanlx' step begin done m =- cons (done begin) $ go m begin- where- go m1 !acc = mkStream $ \st yld sng stp ->- let single a = sng (done $ step acc a)- yieldk a r =- let s = step acc a- in yld (done s) (go r s)- in foldStream (adaptState st) yieldk single stp m1--{-# INLINE scanl' #-}-scanl' :: IsStream t => (b -> a -> b) -> b -> t m a -> t m b-scanl' step begin = scanlx' step begin id------------------------------------------------------------------------------------ Filtering----------------------------------------------------------------------------------{-# INLINE filter #-}-filter :: IsStream t => (a -> Bool) -> t m a -> t m a-filter p m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a | p a = sng a- | otherwise = stp- yieldk a r | p a = yld a (go r)- | otherwise = foldStream st yieldk single stp r- in foldStream st yieldk single stp m1--{-# INLINE take #-}-take :: IsStream t => Int -> t m a -> t m a-take n m = go n m- where- go n1 m1 = mkStream $ \st yld sng stp ->- let yieldk a r = yld a (go (n1 - 1) r)- in if n1 <= 0- then stp- else foldStream st yieldk sng stp m1--{-# INLINE takeWhile #-}-takeWhile :: IsStream t => (a -> Bool) -> t m a -> t m a-takeWhile p m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a | p a = sng a- | otherwise = stp- yieldk a r | p a = yld a (go r)- | otherwise = stp- in foldStream st yieldk single stp m1--{-# INLINE drop #-}-drop :: IsStream t => Int -> t m a -> t m a-drop n m = fromStream $ unShare (go n (toStream m))- where- go n1 m1 = mkStream $ \st yld sng stp ->- let single _ = stp- yieldk _ r = foldStreamShared st yld sng stp $ go (n1 - 1) r- -- Somehow "<=" check performs better than a ">"- in if n1 <= 0- then foldStreamShared st yld sng stp m1- else foldStreamShared st yieldk single stp m1--{-# INLINE dropWhile #-}-dropWhile :: IsStream t => (a -> Bool) -> t m a -> t m a-dropWhile p m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a | p a = stp- | otherwise = sng a- yieldk a r | p a = foldStream st yieldk single stp r- | otherwise = yld a r- in foldStream st yieldk single stp m1------------------------------------------------------------------------------------ Mapping------------------------------------------------------------------------------------ Be careful when modifying this, this uses a consM (|:) deliberately to allow--- other stream types to overload it.-{-# INLINE sequence #-}-sequence :: (IsStream t, MonadAsync m) => t m (m a) -> t m a-sequence m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single ma = ma >>= sng- yieldk ma r = foldStreamShared st yld sng stp $ ma |: go r- in foldStream (adaptState st) yieldk single stp m1------------------------------------------------------------------------------------ Inserting----------------------------------------------------------------------------------{-# INLINE intersperseM #-}-intersperseM :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a-intersperseM a m = prependingStart m- where- prependingStart m1 = mkStream $ \st yld sng stp ->- let yieldk i x = foldStreamShared st yld sng stp $ return i |: go x- in foldStream st yieldk sng stp m1- go m2 = mkStream $ \st yld sng stp ->- let single i = foldStreamShared st yld sng stp $ a |: yield i- yieldk i x = foldStreamShared st yld sng stp $ a |: return i |: go x- in foldStream st yieldk single stp m2--{-# INLINE intersperse #-}-intersperse :: (IsStream t, MonadAsync m) => a -> t m a -> t m a-intersperse a = intersperseM (return a)--{-# INLINE insertBy #-}-insertBy :: IsStream t => (a -> a -> Ordering) -> a -> t m a -> t m a-insertBy cmp x m = go m- where- go m1 = mkStream $ \st yld _ _ ->- let single a = case cmp x a of- GT -> yld a (yield x)- _ -> yld x (yield a)- stop = yld x nil- yieldk a r = case cmp x a of- GT -> yld a (go r)- _ -> yld x (a `cons` r)- in foldStream st yieldk single stop m1----------------------------------------------------------------------------------- Deleting---------------------------------------------------------------------------------{-# INLINE deleteBy #-}-deleteBy :: IsStream t => (a -> a -> Bool) -> a -> t m a -> t m a-deleteBy eq x m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a = if eq x a then stp else sng a- yieldk a r = if eq x a- then foldStream st yld sng stp r- else yld a (go r)- in foldStream st yieldk single stp m1----------------------------------------------------------------------------------- Reordering---------------------------------------------------------------------------------{-# INLINE reverse #-}-reverse :: IsStream t => t m a -> t m a-reverse = foldlS (flip cons) nil------------------------------------------------------------------------------------ Map and Filter----------------------------------------------------------------------------------{-# INLINE mapMaybe #-}-mapMaybe :: IsStream t => (a -> Maybe b) -> t m a -> t m b-mapMaybe f m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a = case f a of- Just b -> sng b- Nothing -> stp- yieldk a r = case f a of- Just b -> yld b $ go r- Nothing -> foldStream (adaptState st) yieldk single stp r- in foldStream (adaptState st) yieldk single stp m1----------------------------------------------------------------------------------- Serial Zipping----------------------------------------------------------------------------------- | Zip two streams serially using a pure zipping function.------ @since 0.1.0-{-# INLINABLE zipWith #-}-zipWith :: IsStream t => (a -> b -> c) -> t m a -> t m b -> t m c-zipWith f = go- where- go mx my = mkStream $ \st yld sng stp -> do- let merge a ra =- let single2 b = sng (f a b)- yield2 b rb = yld (f a b) (go ra rb)- in foldStream (adaptState st) yield2 single2 stp my- let single1 a = merge a nil- yield1 = merge- foldStream (adaptState st) yield1 single1 stp mx---- | Zip two streams serially using a monadic zipping function.------ @since 0.1.0-{-# INLINABLE zipWithM #-}-zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c-zipWithM f m1 m2 = go m1 m2- where- go mx my = mkStream $ \st yld sng stp -> do- let merge a ra =- let runIt x = foldStream st yld sng stp x- single2 b = f a b >>= sng- yield2 b rb = f a b >>= \x -> runIt (x `cons` go ra rb)- in foldStream (adaptState st) yield2 single2 stp my- let single1 a = merge a nil- yield1 = merge- foldStream (adaptState st) yield1 single1 stp mx----------------------------------------------------------------------------------- Merging---------------------------------------------------------------------------------{-# INLINE mergeByM #-}-mergeByM- :: (IsStream t, Monad m)- => (a -> a -> m Ordering) -> t m a -> t m a -> t m a-mergeByM cmp = go- where- go mx my = mkStream $ \st yld sng stp -> do- let mergeWithY a ra =- let stop2 = foldStream st yld sng stp mx- single2 b = do- r <- cmp a b- case r of- GT -> yld b (go (a `cons` ra) nil)- _ -> yld a (go ra (b `cons` nil))- yield2 b rb = do- r <- cmp a b- case r of- GT -> yld b (go (a `cons` ra) rb)- _ -> yld a (go ra (b `cons` rb))- in foldStream st yield2 single2 stop2 my- let stopX = foldStream st yld sng stp my- singleX a = mergeWithY a nil- yieldX = mergeWithY- foldStream st yieldX singleX stopX mx--{-# INLINABLE mergeBy #-}-mergeBy- :: (IsStream t, Monad m)- => (a -> a -> Ordering) -> t m a -> t m a -> t m a-mergeBy cmp = mergeByM (\a b -> return $ cmp a b)----------------------------------------------------------------------------------- Transformation comprehensions---------------------------------------------------------------------------------{-# INLINE the #-}-the :: (Eq a, IsStream t, Monad m) => t m a -> m (Maybe a)-the m = do- r <- uncons m- case r of- Nothing -> return Nothing- Just (h, t) -> go h t- where- go h m1 =- let single a | h == a = return $ Just h- | otherwise = return Nothing- yieldk a r | h == a = go h r- | otherwise = return Nothing- in foldStream defState yieldk single (return $ Just h) m1----------------------------------------------------------------------------------- Alternative & MonadPlus---------------------------------------------------------------------------------_alt :: Stream m a -> Stream m a -> Stream m a-_alt m1 m2 = mkStream $ \st yld sng stp ->- let stop = foldStream st yld sng stp m2- in foldStream st yld sng stop m1----------------------------------------------------------------------------------- MonadReader---------------------------------------------------------------------------------{-# INLINABLE withLocal #-}-withLocal :: MonadReader r m => (r -> r) -> Stream m a -> Stream m a-withLocal f m =- mkStream $ \st yld sng stp ->- let single = local f . sng- yieldk a r = local f $ yld a (withLocal f r)- in foldStream st yieldk single (local f stp) m----------------------------------------------------------------------------------- MonadError---------------------------------------------------------------------------------{---- XXX handle and test cross thread state transfer-withCatchError- :: MonadError e m- => Stream m a -> (e -> Stream m a) -> Stream m a-withCatchError m h =- mkStream $ \_ stp sng yld ->- let run x = unStream x Nothing stp sng yieldk- handle r = r `catchError` \e -> run $ h e- yieldk a r = yld a (withCatchError r h)- in handle $ run m--}
− src/Streamly/Streams/StreamK/Type.hs
@@ -1,1043 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ViewPatterns #-}-#if __GLASGOW_HASKELL__ >= 806-{-# LANGUAGE QuantifiedConstraints #-}-#endif-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX--#include "../inline.hs"---- |--- Module : Streamly.Streams.StreamK.Type--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--------- Continuation passing style (CPS) stream implementation. The symbol 'K' below--- denotes a function as well as a Kontinuation.----module Streamly.Streams.StreamK.Type- (- -- * A class for streams- IsStream (..)- , adapt-- -- * The stream type- , Stream ()-- -- * Construction- , mkStream- , fromStopK- , fromYieldK- , consK-- -- * Elimination- , foldStream- , foldStreamShared- , foldStreamSVar-- -- * foldr/build- , foldrM- , foldrS- , foldrSM- , build- , buildS- , buildM- , buildSM- , sharedM- , augmentS- , augmentSM-- -- instances- , cons- , (.:)- , consMStream- , consMBy- , yieldM- , yield-- , nil- , nilM- , conjoin- , serial- , map- , mapM- , mapMSerial- , unShare- , concatMapBy- , concatMap- , bindWith-- , Streaming -- deprecated- )-where--import Control.Monad (void, ap, (>=>))-import Control.Monad.IO.Class (MonadIO(liftIO))-import Control.Monad.Trans.Class (MonadTrans(lift))-#if __GLASGOW_HASKELL__ >= 800-import Data.Kind (Type)-#endif-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif-import Prelude hiding (map, mapM, concatMap, foldr)--import Streamly.Internal.Data.SVar----------------------------------------------------------------------------------- Basic stream type----------------------------------------------------------------------------------- | The type @Stream m a@ represents a monadic stream of values of type 'a'--- constructed using actions in monad 'm'. It uses stop, singleton and yield--- continuations equivalent to the following direct style type:------ @--- data Stream m a = Stop | Singleton a | Yield a (Stream m a)--- @------ To facilitate parallel composition we maintain a local state in an 'SVar'--- that is shared across and is used for synchronization of the streams being--- composed.------ The singleton case can be expressed in terms of stop and yield but we have--- it as a separate case to optimize composition operations for streams with--- single element. We build singleton streams in the implementation of 'pure'--- for Applicative and Monad, and in 'lift' for MonadTrans.------ XXX remove the Stream type parameter from State as it is always constant.--- We can remove it from SVar as well----newtype Stream m a =- MkStream (forall r.- State Stream m a -- state- -> (a -> Stream m a -> m r) -- yield- -> (a -> m r) -- singleton- -> m r -- stop- -> m r- )----------------------------------------------------------------------------------- Types that can behave as a Stream---------------------------------------------------------------------------------infixr 5 `consM`-infixr 5 |:---- XXX Use a different SVar based on the stream type. But we need to make sure--- that we do not lose performance due to polymorphism.------ | Class of types that can represent a stream of elements of some type 'a' in--- some monad 'm'.------ @since 0.2.0-class-#if __GLASGOW_HASKELL__ >= 806- ( forall m a. MonadAsync m => Semigroup (t m a)- , forall m a. MonadAsync m => Monoid (t m a)- , forall m. Monad m => Functor (t m)- , forall m. MonadAsync m => Applicative (t m)- ) =>-#endif- IsStream t where- toStream :: t m a -> Stream m a- fromStream :: Stream m a -> t m a- -- | Constructs a stream by adding a monadic action at the head of an- -- existing stream. For example:- --- -- @- -- > toList $ getLine \`consM` getLine \`consM` nil- -- hello- -- world- -- ["hello","world"]- -- @- --- -- /Concurrent (do not use 'parallely' to construct infinite streams)/- --- -- @since 0.2.0- consM :: MonadAsync m => m a -> t m a -> t m a- -- | Operator equivalent of 'consM'. We can read it as "@parallel colon@"- -- to remember that @|@ comes before ':'.- --- -- @- -- > toList $ getLine |: getLine |: nil- -- hello- -- world- -- ["hello","world"]- -- @- --- -- @- -- let delay = threadDelay 1000000 >> print 1- -- drain $ serially $ delay |: delay |: delay |: nil- -- drain $ parallely $ delay |: delay |: delay |: nil- -- @- --- -- /Concurrent (do not use 'parallely' to construct infinite streams)/- --- -- @since 0.2.0- (|:) :: MonadAsync m => m a -> t m a -> t m a- -- We can define (|:) just as 'consM' but it is defined explicitly for each- -- type because we want to use SPECIALIZE pragma on the definition.---- | Same as 'IsStream'.------ @since 0.1.0-{-# DEPRECATED Streaming "Please use IsStream instead." #-}-type Streaming = IsStream------------------------------------------------------------------------------------ Type adapting combinators------------------------------------------------------------------------------------ XXX Move/reset the State here by reconstructing the stream with cleared--- state. Can we make sure we do not do that when t1 = t2? If we do this then--- we do not need to do that explicitly using svarStyle. It would act as--- unShare when the stream type is the same.------ | Adapt any specific stream type to any other specific stream type.------ @since 0.1.0-adapt :: (IsStream t1, IsStream t2) => t1 m a -> t2 m a-adapt = fromStream . toStream----------------------------------------------------------------------------------- Building a stream----------------------------------------------------------------------------------- XXX The State is always parameterized by "Stream" which means State is not--- different for different stream types. So we have to manually make sure that--- when converting from one stream to another we migrate the state correctly.--- This can be fixed if we use a different SVar type for different streams.--- Currently we always use "SVar Stream" and therefore a different State type--- parameterized by that stream.------ XXX Since t is coercible we should be able to coerce k--- mkStream k = fromStream $ MkStream $ coerce k------ | Build a stream from an 'SVar', a stop continuation, a singleton stream--- continuation and a yield continuation.-{-# INLINE_EARLY mkStream #-}-mkStream :: IsStream t- => (forall r. State Stream m a- -> (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> m r)- -> t m a-mkStream k = fromStream $ MkStream $ \st yld sng stp ->- let yieldk a r = yld a (toStream r)- in k st yieldk sng stp--{-# RULES "mkStream from stream" mkStream = mkStreamFromStream #-}-mkStreamFromStream :: IsStream t- => (forall r. State Stream m a- -> (a -> Stream m a -> m r)- -> (a -> m r)- -> m r- -> m r)- -> t m a-mkStreamFromStream k = fromStream $ MkStream k--{-# RULES "mkStream stream" mkStream = mkStreamStream #-}-mkStreamStream- :: (forall r. State Stream m a- -> (a -> Stream m a -> m r)- -> (a -> m r)- -> m r- -> m r)- -> Stream m a-mkStreamStream = MkStream---- | A terminal function that has no continuation to follow.-type StopK m = forall r. m r -> m r---- | A monadic continuation, it is a function that yields a value of type "a"--- and calls the argument (a -> m r) as a continuation with that value. We can--- also think of it as a callback with a handler (a -> m r). Category--- theorists call it a codensity type, a special type of right kan extension.-type YieldK m a = forall r. (a -> m r) -> m r--_wrapM :: Monad m => m a -> YieldK m a-_wrapM m = \k -> m >>= k---- | Make an empty stream from a stop function.-fromStopK :: IsStream t => StopK m -> t m a-fromStopK k = mkStream $ \_ _ _ stp -> k stp---- | Make a singleton stream from a yield function.-fromYieldK :: IsStream t => YieldK m a -> t m a-fromYieldK k = mkStream $ \_ _ sng _ -> k sng---- | Add a yield function at the head of the stream.-consK :: IsStream t => YieldK m a -> t m a -> t m a-consK k r = mkStream $ \_ yld _ _ -> k (\x -> yld x r)---- XXX Build a stream from a repeating callback function.----------------------------------------------------------------------------------- Construction---------------------------------------------------------------------------------infixr 5 `cons`---- faster than consM because there is no bind.--- | Construct a stream by adding a pure value at the head of an existing--- stream. For serial streams this is the same as @(return a) \`consM` r@ but--- more efficient. For concurrent streams this is not concurrent whereas--- 'consM' is concurrent. For example:------ @--- > toList $ 1 \`cons` 2 \`cons` 3 \`cons` nil--- [1,2,3]--- @------ @since 0.1.0-{-# INLINE_NORMAL cons #-}-cons :: IsStream t => a -> t m a -> t m a-cons a r = mkStream $ \_ yld _ _ -> yld a r--infixr 5 .:---- | Operator equivalent of 'cons'.------ @--- > toList $ 1 .: 2 .: 3 .: nil--- [1,2,3]--- @------ @since 0.1.1-{-# INLINE (.:) #-}-(.:) :: IsStream t => a -> t m a -> t m a-(.:) = cons---- | An empty stream.------ @--- > toList nil--- []--- @------ @since 0.1.0-{-# INLINE_NORMAL nil #-}-nil :: IsStream t => t m a-nil = mkStream $ \_ _ _ stp -> stp---- | An empty stream producing a side effect.------ @--- > toList (nilM (print "nil"))--- "nil"--- []--- @------ /Internal/-{-# INLINE_NORMAL nilM #-}-nilM :: (IsStream t, Monad m) => m b -> t m a-nilM m = mkStream $ \_ _ _ stp -> m >> stp--{-# INLINE_NORMAL yield #-}-yield :: IsStream t => a -> t m a-yield a = mkStream $ \_ _ single _ -> single a--{-# INLINE_NORMAL yieldM #-}-yieldM :: (Monad m, IsStream t) => m a -> t m a-yieldM m = fromStream $ mkStream $ \_ _ single _ -> m >>= single---- XXX specialize to IO?-{-# INLINE consMBy #-}-consMBy :: (IsStream t, MonadAsync m) => (t m a -> t m a -> t m a)- -> m a -> t m a -> t m a-consMBy f m r = (fromStream $ yieldM m) `f` r----------------------------------------------------------------------------------- Folding a stream----------------------------------------------------------------------------------- | Fold a stream by providing an SVar, a stop continuation, a singleton--- continuation and a yield continuation. The stream would share the current--- SVar passed via the State.-{-# INLINE_EARLY foldStreamShared #-}-foldStreamShared- :: IsStream t- => State Stream m a- -> (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> t m a- -> m r-foldStreamShared st yld sng stp m =- let yieldk a x = yld a (fromStream x)- MkStream k = toStream m- in k st yieldk sng stp---- XXX write a similar rule for foldStream as well?-{-# RULES "foldStreamShared from stream"- foldStreamShared = foldStreamSharedStream #-}-foldStreamSharedStream- :: State Stream m a- -> (a -> Stream m a -> m r)- -> (a -> m r)- -> m r- -> Stream m a- -> m r-foldStreamSharedStream st yld sng stp m =- let MkStream k = toStream m- in k st yld sng stp---- | Fold a stream by providing a State, stop continuation, a singleton--- continuation and a yield continuation. The stream will not use the SVar--- passed via State.-{-# INLINE foldStream #-}-foldStream- :: IsStream t- => State Stream m a- -> (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> t m a- -> m r-foldStream st yld sng stp m =- let yieldk a x = yld a (fromStream x)- MkStream k = toStream m- in k (adaptState st) yieldk sng stp---- Run the stream using a run function associated with the SVar that runs the--- streams with a captured snapshot of the monadic state.-{-# INLINE foldStreamSVar #-}-foldStreamSVar- :: (IsStream t, MonadIO m)- => SVar Stream m a- -> State Stream m a -- state- -> (a -> t m a -> m r) -- yield- -> (a -> m r) -- singleton- -> m r -- stop- -> t m a- -> m ()-foldStreamSVar sv st yld sng stp m =- let mrun = runInIO $ svarMrun sv- in void $ liftIO $ mrun $ foldStreamShared st yld sng stp m------------------------------------------------------------------------------------ Instances------------------------------------------------------------------------------------ NOTE: specializing the function outside the instance definition seems to--- improve performance quite a bit at times, even if we have the same--- SPECIALIZE in the instance definition.-{-# INLINE consMStream #-}-{-# SPECIALIZE consMStream :: IO a -> Stream IO a -> Stream IO a #-}-consMStream :: (Monad m) => m a -> Stream m a -> Stream m a-consMStream m r = MkStream $ \_ yld _ _ -> m >>= \a -> yld a r------------------------------------------------------------------------------------ IsStream Stream----------------------------------------------------------------------------------instance IsStream Stream where- toStream = id- fromStream = id-- {-# INLINE consM #-}- {-# SPECIALIZE consM :: IO a -> Stream IO a -> Stream IO a #-}- consM :: Monad m => m a -> Stream m a -> Stream m a- consM = consMStream-- {-# INLINE (|:) #-}- {-# SPECIALIZE (|:) :: IO a -> Stream IO a -> Stream IO a #-}- (|:) :: Monad m => m a -> Stream m a -> Stream m a- (|:) = consMStream------------------------------------------------------------------------------------ foldr/build fusion------------------------------------------------------------------------------------ XXX perhaps we can just use foldrSM/buildM everywhere as they are more--- general and cover foldrS/buildS as well.---- | The function 'f' decides how to reconstruct the stream. We could--- reconstruct using a shared state (SVar) or without sharing the state.----{-# INLINE foldrSWith #-}-foldrSWith :: IsStream t- => (forall r. State Stream m b- -> (b -> t m b -> m r)- -> (b -> m r)- -> m r- -> t m b- -> m r)- -> (a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrSWith f step final m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let run x = f st yld sng stp x- stop = run final- single a = run $ step a final- yieldk a r = run $ step a (go r)- -- XXX if type a and b are the same we do not need adaptState, can we- -- save some perf with that?- -- XXX since we are using adaptState anyway here we can use- -- foldStreamShared instead, will that save some perf?- in foldStream (adaptState st) yieldk single stop m1---- XXX we can use rewrite rules just for foldrSWith, if the function f is the--- same we can rewrite it.---- | Fold sharing the SVar state within the reconstructed stream-{-# INLINE_NORMAL foldrSShared #-}-foldrSShared :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrSShared = foldrSWith foldStreamShared---- XXX consM is a typeclass method, therefore rewritten already. Instead maybe--- we can make consM polymorphic using rewrite rules.--- {-# RULES "foldrSShared/id" foldrSShared consM nil = \x -> x #-}-{-# RULES "foldrSShared/nil"- forall k z. foldrSShared k z nil = z #-}-{-# RULES "foldrSShared/single"- forall k z x. foldrSShared k z (yield x) = k x z #-}--- {-# RULES "foldrSShared/app" [1]--- forall ys. foldrSShared consM ys = \xs -> xs `conjoin` ys #-}---- | Lazy right associative fold to a stream.-{-# INLINE_NORMAL foldrS #-}-foldrS :: IsStream t => (a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrS = foldrSWith foldStream--{-# RULES "foldrS/id" foldrS cons nil = \x -> x #-}-{-# RULES "foldrS/nil" forall k z. foldrS k z nil = z #-}--- See notes in GHC.Base about this rule--- {-# RULES "foldr/cons"--- forall k z x xs. foldrS k z (x `cons` xs) = k x (foldrS k z xs) #-}-{-# RULES "foldrS/single" forall k z x. foldrS k z (yield x) = k x z #-}--- {-# RULES "foldrS/app" [1]--- forall ys. foldrS cons ys = \xs -> xs `conjoin` ys #-}------------------------------------------------------------------------------------ foldrS with monadic cons i.e. consM----------------------------------------------------------------------------------{-# INLINE foldrSMWith #-}-foldrSMWith :: (IsStream t, Monad m)- => (forall r. State Stream m b- -> (b -> t m b -> m r)- -> (b -> m r)- -> m r- -> t m b- -> m r)- -> (m a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrSMWith f step final m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let run x = f st yld sng stp x- stop = run final- single a = run $ step (return a) final- yieldk a r = run $ step (return a) (go r)- in foldStream (adaptState st) yieldk single stop m1--{-# INLINE_NORMAL foldrSM #-}-foldrSM :: (IsStream t, Monad m)- => (m a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrSM = foldrSMWith foldStream---- {-# RULES "foldrSM/id" foldrSM consM nil = \x -> x #-}-{-# RULES "foldrSM/nil" forall k z. foldrSM k z nil = z #-}-{-# RULES "foldrSM/single" forall k z x. foldrSM k z (yieldM x) = k x z #-}--- {-# RULES "foldrSM/app" [1]--- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}---- Like foldrSM but sharing the SVar state within the recostructed stream.-{-# INLINE_NORMAL foldrSMShared #-}-foldrSMShared :: (IsStream t, Monad m)- => (m a -> t m b -> t m b) -> t m b -> t m a -> t m b-foldrSMShared = foldrSMWith foldStreamShared---- {-# RULES "foldrSM/id" foldrSM consM nil = \x -> x #-}-{-# RULES "foldrSMShared/nil"- forall k z. foldrSMShared k z nil = z #-}-{-# RULES "foldrSMShared/single"- forall k z x. foldrSMShared k z (yieldM x) = k x z #-}--- {-# RULES "foldrSM/app" [1]--- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}------------------------------------------------------------------------------------ build----------------------------------------------------------------------------------{-# INLINE_NORMAL build #-}-build :: IsStream t => forall a. (forall b. (a -> b -> b) -> b -> b) -> t m a-build g = g cons nil--{-# RULES "foldrM/build"- forall k z (g :: forall b. (a -> b -> b) -> b -> b).- foldrM k z (build g) = g k z #-}--{-# RULES "foldrS/build"- forall k z (g :: forall b. (a -> b -> b) -> b -> b).- foldrS k z (build g) = g k z #-}--{-# RULES "foldrS/cons/build"- forall k z x (g :: forall b. (a -> b -> b) -> b -> b).- foldrS k z (x `cons` build g) = k x (g k z) #-}--{-# RULES "foldrSShared/build"- forall k z (g :: forall b. (a -> b -> b) -> b -> b).- foldrSShared k z (build g) = g k z #-}--{-# RULES "foldrSShared/cons/build"- forall k z x (g :: forall b. (a -> b -> b) -> b -> b).- foldrSShared k z (x `cons` build g) = k x (g k z) #-}---- build a stream by applying cons and nil to a build function-{-# INLINE_NORMAL buildS #-}-buildS :: IsStream t => ((a -> t m a -> t m a) -> t m a -> t m a) -> t m a-buildS g = g cons nil--{-# RULES "foldrS/buildS"- forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a).- foldrS k z (buildS g) = g k z #-}--{-# RULES "foldrS/cons/buildS"- forall k z x (g :: (a -> t m a -> t m a) -> t m a -> t m a).- foldrS k z (x `cons` buildS g) = k x (g k z) #-}--{-# RULES "foldrSShared/buildS"- forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a).- foldrSShared k z (buildS g) = g k z #-}--{-# RULES "foldrSShared/cons/buildS"- forall k z x (g :: (a -> t m a -> t m a) -> t m a -> t m a).- foldrSShared k z (x `cons` buildS g) = k x (g k z) #-}---- build a stream by applying consM and nil to a build function-{-# INLINE_NORMAL buildSM #-}-buildSM :: (IsStream t, MonadAsync m)- => ((m a -> t m a -> t m a) -> t m a -> t m a) -> t m a-buildSM g = g consM nil--{-# RULES "foldrSM/buildSM"- forall k z (g :: (m a -> t m a -> t m a) -> t m a -> t m a).- foldrSM k z (buildSM g) = g k z #-}--{-# RULES "foldrSMShared/buildSM"- forall k z (g :: (m a -> t m a -> t m a) -> t m a -> t m a).- foldrSMShared k z (buildSM g) = g k z #-}---- Disabled because this may not fire as consM is a class Op-{--{-# RULES "foldrS/consM/buildSM"- forall k z x (g :: (m a -> t m a -> t m a) -> t m a -> t m a)- . foldrSM k z (x `consM` buildSM g)- = k x (g k z)-#-}--}---- Build using monadic build functions (continuations) instead of--- reconstructing a stream.-{-# INLINE_NORMAL buildM #-}-buildM :: (IsStream t, MonadAsync m)- => (forall r. (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> m r- )- -> t m a-buildM g = mkStream $ \st yld sng stp ->- g (\a r -> foldStream st yld sng stp (return a `consM` r)) sng stp---- | Like 'buildM' but shares the SVar state across computations.-{-# INLINE_NORMAL sharedM #-}-sharedM :: (IsStream t, MonadAsync m)- => (forall r. (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> m r- )- -> t m a-sharedM g = mkStream $ \st yld sng stp ->- g (\a r -> foldStreamShared st yld sng stp (return a `consM` r)) sng stp------------------------------------------------------------------------------------ augment----------------------------------------------------------------------------------{-# INLINE_NORMAL augmentS #-}-augmentS :: IsStream t- => ((a -> t m a -> t m a) -> t m a -> t m a) -> t m a -> t m a-augmentS g xs = g cons xs--{-# RULES "augmentS/nil"- forall (g :: (a -> t m a -> t m a) -> t m a -> t m a).- augmentS g nil = buildS g- #-}--{-# RULES "foldrS/augmentS"- forall k z xs (g :: (a -> t m a -> t m a) -> t m a -> t m a).- foldrS k z (augmentS g xs) = g k (foldrS k z xs)- #-}--{-# RULES "augmentS/buildS"- forall (g :: (a -> t m a -> t m a) -> t m a -> t m a)- (h :: (a -> t m a -> t m a) -> t m a -> t m a).- augmentS g (buildS h) = buildS (\c n -> g c (h c n))- #-}--{-# INLINE_NORMAL augmentSM #-}-augmentSM :: (IsStream t, MonadAsync m)- => ((m a -> t m a -> t m a) -> t m a -> t m a) -> t m a -> t m a-augmentSM g xs = g consM xs--{-# RULES "augmentSM/nil"- forall (g :: (m a -> t m a -> t m a) -> t m a -> t m a).- augmentSM g nil = buildSM g- #-}--{-# RULES "foldrSM/augmentSM"- forall k z xs (g :: (m a -> t m a -> t m a) -> t m a -> t m a).- foldrSM k z (augmentSM g xs) = g k (foldrSM k z xs)- #-}--{-# RULES "augmentSM/buildSM"- forall (g :: (m a -> t m a -> t m a) -> t m a -> t m a)- (h :: (m a -> t m a -> t m a) -> t m a -> t m a).- augmentSM g (buildSM h) = buildSM (\c n -> g c (h c n))- #-}------------------------------------------------------------------------------------ Experimental foldrM/buildM------------------------------------------------------------------------------------ | Lazy right fold with a monadic step function.-{-# INLINE_NORMAL foldrM #-}-foldrM :: IsStream t => (a -> m b -> m b) -> m b -> t m a -> m b-foldrM step acc m = go m- where- go m1 =- let stop = acc- single a = step a acc- yieldk a r = step a (go r)- in foldStream defState yieldk single stop m1--{-# INLINE_NORMAL foldrMKWith #-}-foldrMKWith- :: (State Stream m a- -> (a -> t m a -> m b)- -> (a -> m b)- -> m b- -> t m a- -> m b)- -> (a -> m b -> m b)- -> m b- -> ((a -> t m a -> m b) -> (a -> m b) -> m b -> m b)- -> m b-foldrMKWith f step acc g = go g- where- go k =- let stop = acc- single a = step a acc- yieldk a r = step a (go (\yld sng stp -> f defState yld sng stp r))- in k yieldk single stop--{--{-# RULES "foldrM/buildS"- forall k z (g :: (a -> t m a -> t m a) -> t m a -> t m a)- . foldrM k z (buildS g)- = g k z-#-}--}--- XXX in which case will foldrM/buildM fusion be useful?-{-# RULES "foldrM/buildM"- forall step acc (g :: (forall r.- (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> m r- )).- foldrM step acc (buildM g) = foldrMKWith foldStream step acc g- #-}--{-# RULES "foldrM/sharedM"- forall step acc (g :: (forall r.- (a -> t m a -> m r)- -> (a -> m r)- -> m r- -> m r- )).- foldrM step acc (sharedM g) = foldrMKWith foldStreamShared step acc g- #-}----------------------------------------------------------------------------------- Semigroup----------------------------------------------------------------------------------- | Polymorphic version of the 'Semigroup' operation '<>' of 'SerialT'.--- Appends two streams sequentially, yielding all elements from the first--- stream, and then all elements from the second stream.------ @since 0.2.0-{-# INLINE serial #-}-serial :: IsStream t => t m a -> t m a -> t m a-serial xs ys = augmentS (\c n -> foldrS c n xs) ys-{--serial m1 m2 = go m1- where- go m = mkStream $ \st yld sng stp ->- let stop = foldStream st yld sng stp m2- single a = yld a m2- yieldk a r = yld a (go r)- in foldStream st yieldk single stop m--}---- join/merge/append streams depending on consM-{-# INLINE conjoin #-}-conjoin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a-conjoin xs ys = augmentSM (\c n -> foldrSM c n xs) ys--instance Semigroup (Stream m a) where- (<>) = serial----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance Monoid (Stream m a) where- mempty = nil- mappend = (<>)------------------------------------------------------------------------------------ Functor----------------------------------------------------------------------------------#if __GLASGOW_HASKELL__ < 800-#define Type *-#endif--- Note eta expanded-{-# INLINE_LATE mapFB #-}-mapFB :: forall (t :: (Type -> Type) -> Type -> Type) b m a.- (b -> t m b -> t m b) -> (a -> b) -> a -> t m b -> t m b-mapFB c f = \x ys -> c (f x) ys-#undef Type--{-# RULES-"mapFB/mapFB" forall c f g. mapFB (mapFB c f) g = mapFB c (f . g)-"mapFB/id" forall c. mapFB c (\x -> x) = c- #-}--{-# INLINE map #-}-map :: IsStream t => (a -> b) -> t m a -> t m b-map f xs = buildS (\c n -> foldrS (mapFB c f) n xs)---- XXX This definition might potentially be more efficient, but the cost in the--- benchmark is dominated by unfoldrM cost so we cannot correctly determine--- differences in the mapping cost. We should perhaps deduct the cost of--- unfoldrM from the benchmarks and then compare.-{--map f m = go m- where- go m1 =- mkStream $ \st yld sng stp ->- let single = sng . f- yieldk a r = yld (f a) (go r)- in foldStream (adaptState st) yieldk single stp m1--}--{-# INLINE_LATE mapMFB #-}-mapMFB :: Monad m => (m b -> t m b -> t m b) -> (a -> m b) -> m a -> t m b -> t m b-mapMFB c f = \x ys -> c (x >>= f) ys--{-# RULES- "mapMFB/mapMFB" forall c f g. mapMFB (mapMFB c f) g = mapMFB c (f >=> g)- #-}--- XXX These rules may never fire because pure/return type class rules will--- fire first.-{--"mapMFB/pure" forall c. mapMFB c (\x -> pure x) = c-"mapMFB/return" forall c. mapMFB c (\x -> return x) = c--}---- Be careful when modifying this, this uses a consM (|:) deliberately to allow--- other stream types to overload it.-{-# INLINE mapM #-}-mapM :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m b-mapM f = foldrSShared (\x xs -> f x `consM` xs) nil--- See note under map definition above.-{--mapM f m = go m- where- go m1 = mkStream $ \st yld sng stp ->- let single a = f a >>= sng- yieldk a r = foldStreamShared st yld sng stp $ f a |: go r- in foldStream (adaptState st) yieldk single stp m1- -}---- This is experimental serial version supporting fusion.------ XXX what if we do not want to fuse two concurrent mapMs?--- XXX we can combine two concurrent mapM only if the SVar is of the same type--- So for now we use it only for serial streams.--- XXX fusion would be easier for monomoprhic stream types.--- {-# RULES "mapM serial" mapM = mapMSerial #-}-{-# INLINE mapMSerial #-}-mapMSerial :: MonadAsync m => (a -> m b) -> Stream m a -> Stream m b-mapMSerial f xs = buildSM (\c n -> foldrSMShared (mapMFB c f) n xs)---- XXX in fact use the Stream type everywhere and only use polymorphism in the--- high level modules/prelude.-instance Monad m => Functor (Stream m) where- fmap = map------------------------------------------------------------------------------------ Transformers----------------------------------------------------------------------------------instance MonadTrans Stream where- lift = yieldM------------------------------------------------------------------------------------ Nesting------------------------------------------------------------------------------------ | Detach a stream from an SVar-{-# INLINE unShare #-}-unShare :: IsStream t => t m a -> t m a-unShare x = mkStream $ \st yld sng stp ->- foldStream st yld sng stp x---- XXX This is just concatMapBy with arguments flipped. We need to keep this--- instead of using a concatMap style definition because the bind--- implementation in Async and WAsync streams show significant perf degradation--- if the argument order is changed.-{-# INLINE bindWith #-}-bindWith- :: IsStream t- => (forall c. t m c -> t m c -> t m c)- -> t m a- -> (a -> t m b)- -> t m b-bindWith par m1 f = go m1- where- go m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single a = foldShared $ unShare (f a)- yieldk a r = foldShared $ unShare (f a) `par` go r- in foldStream (adaptState st) yieldk single stp m---- XXX express in terms of foldrS?--- XXX can we use a different stream type for the generated stream being--- falttened so that we can combine them differently and keep the resulting--- stream different?--- XXX do we need specialize to IO?--- XXX can we optimize when c and a are same, by removing the forall using--- rewrite rules with type applications?---- | Perform a 'concatMap' using a specified concat strategy. The first--- argument specifies a merge or concat function that is used to merge the--- streams generated by the map function. For example, the concat function--- could be 'serial', 'parallel', 'async', 'ahead' or any other zip or merge--- function.------ @since 0.7.0-{-# INLINE concatMapBy #-}-concatMapBy- :: IsStream t- => (forall c. t m c -> t m c -> t m c)- -> (a -> t m b)- -> t m a- -> t m b-concatMapBy par f xs = bindWith par xs f--{-# INLINE concatMap #-}-concatMap :: IsStream t => (a -> t m b) -> t m a -> t m b-concatMap f m = fromStream $- concatMapBy serial- (\a -> adapt $ toStream $ f a)- (adapt $ toStream m)--{---- Fused version.--- XXX This fuses but when the stream is nil this performs poorly.--- The filterAllOut benchmark degrades. Need to investigate and fix that.-{-# INLINE concatMap #-}-concatMap :: IsStream t => (a -> t m b) -> t m a -> t m b-concatMap f xs = buildS- (\c n -> foldrS (\x b -> foldrS c b (f x)) n xs)---- Stream polymorphic concatMap implementation--- XXX need to use buildSM/foldrSMShared for parallel behavior--- XXX unShare seems to degrade the fused performance-{-# INLINE_EARLY concatMap_ #-}-concatMap_ :: IsStream t => (a -> t m b) -> t m a -> t m b-concatMap_ f xs = buildS- (\c n -> foldrSShared (\x b -> foldrSShared c b (unShare $ f x)) n xs)--}--instance Monad m => Applicative (Stream m) where- {-# INLINE pure #-}- pure = yield- {-# INLINE (<*>) #-}- (<*>) = ap---- NOTE: even though concatMap for StreamD is 3x faster compared to StreamK,--- the monad instance of StreamD is slower than StreamK after foldr/build--- fusion.-instance Monad m => Monad (Stream m) where- {-# INLINE return #-}- return = pure- {-# INLINE (>>=) #-}- (>>=) = flip concatMap--{---- ConcatMap recursively on itself using a merge strategy.-concatLoopBy :: IsStream t- => (forall c. t m c -> t m c -> t m c)- -> (a -> t m a) -> t m a-concatLoopBy = undefined---- This is mfix. Put another way, concatMap recursively on the output of a--- stream. Compare with iterate.-concatLoop :: IsStream t => (a -> t m a) -> t m a-concatLoop = concatLoopBy serial--instance MonadFix (Stream m) where- mfix = concatLoop---- The SVar implementation is something similar to concatFeedBack, so maybe--- there is an opportunity to share the implementation here. In an SVar we run--- a part of an action (a stream), it yields an output and the rest of the--- stream, we yield the output and queue back the rest of the stream for--- further evaluation. Also see unfoldrA.------ There could be multiple variants of this combinator, for example use a--- specific way of concating i.e. concatLoopBy. Which also includes combinators--- with different stop behaviors. For example if the Left values are errors we--- can stop the whole composition on errors or on specific errors.------ We can also flip the serial/ahead append behavior e.g. instead of processing--- the Left output after the original stream we can reverse the order.------ | Concat map on the 'Left' output of a stream and merge it back into the--- stream. The right output is yielded in the output stream.-concatFeedBack :: IsStream t- => (b -> t m (Either a b)) -> t m (Either a b) -> t m a-concatFeedBack = undefined---- Compare this with unfoldr. Start with a seed stream and generate a stream--- with a value and new seeds. The new seeds are fed back to generate a seed--- stream and so on. This is a stream level unfoldr.-concatUnfoldr :: IsStream t- => (b -> t m (Maybe (a, b))) -> t m (Maybe (a, b)) -> t m a-concatUnfoldr = undefined--}
− src/Streamly/Streams/Zip.hs
@@ -1,236 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving#-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-} -- XXX---- |--- Module : Streamly.Streams.Zip--- Copyright : (c) 2017 Harendra Kumar------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-------module Streamly.Streams.Zip- (- K.zipWith- , K.zipWithM- , zipAsyncWith- , zipAsyncWithM-- , ZipSerialM- , ZipSerial- , ZipStream -- deprecated- , zipSerially- , zipping -- deprecated-- , ZipAsyncM- , ZipAsync- , zipAsyncly- , zippingAsync -- deprecated- )-where--import Control.Applicative (liftA2)-import Control.DeepSeq (NFData(..))-#if MIN_VERSION_deepseq(1,4,3)-import Control.DeepSeq (NFData1(..))-#endif-import Data.Functor.Identity (Identity, runIdentity)-import Data.Foldable (fold)-#if __GLASGOW_HASKELL__ < 808-import Data.Semigroup (Semigroup(..))-#endif-import GHC.Exts (IsList(..), IsString(..))-import Text.Read (Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec,- readListPrecDefault)-import Prelude hiding (map, repeat, zipWith)--import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream)-import Streamly.Streams.Async (mkAsync')-import Streamly.Streams.Serial (map)-import Streamly.Internal.Data.SVar (MonadAsync, adaptState)--import qualified Streamly.Streams.Prelude as P-import qualified Streamly.Streams.StreamK as K--#include "Instances.hs"----------------------------------------------------------------------------------- Serially Zipping Streams----------------------------------------------------------------------------------- | The applicative instance of 'ZipSerialM' zips a number of streams serially--- i.e. it produces one element from each stream serially and then zips all--- those elements.------ @--- main = (toList . 'zipSerially' $ (,,) \<$\> s1 \<*\> s2 \<*\> s3) >>= print--- where s1 = fromFoldable [1, 2]--- s2 = fromFoldable [3, 4]--- s3 = fromFoldable [5, 6]--- @--- @--- [(1,3,5),(2,4,6)]--- @------ The 'Semigroup' instance of this type works the same way as that of--- 'SerialT'.------ @since 0.2.0-newtype ZipSerialM m a = ZipSerialM {getZipSerialM :: Stream m a}- deriving (Semigroup, Monoid)---- |--- @since 0.1.0-{-# DEPRECATED ZipStream "Please use 'ZipSerialM' instead." #-}-type ZipStream = ZipSerialM---- | An IO stream whose applicative instance zips streams serially.------ @since 0.2.0-type ZipSerial = ZipSerialM IO---- | Fix the type of a polymorphic stream as 'ZipSerialM'.------ @since 0.2.0-zipSerially :: IsStream t => ZipSerialM m a -> t m a-zipSerially = K.adapt---- | Same as 'zipSerially'.------ @since 0.1.0-{-# DEPRECATED zipping "Please use zipSerially instead." #-}-zipping :: IsStream t => ZipSerialM m a -> t m a-zipping = zipSerially--consMZip :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a-consMZip m ms = fromStream $ K.consMStream m (toStream ms)--instance IsStream ZipSerialM where- toStream = getZipSerialM- fromStream = ZipSerialM-- {-# INLINE consM #-}- {-# SPECIALIZE consM :: IO a -> ZipSerialM IO a -> ZipSerialM IO a #-}- consM :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a- consM = consMZip-- {-# INLINE (|:) #-}- {-# SPECIALIZE (|:) :: IO a -> ZipSerialM IO a -> ZipSerialM IO a #-}- (|:) :: Monad m => m a -> ZipSerialM m a -> ZipSerialM m a- (|:) = consMZip--LIST_INSTANCES(ZipSerialM)-NFDATA1_INSTANCE(ZipSerialM)--instance Monad m => Functor (ZipSerialM m) where- fmap = map--instance Monad m => Applicative (ZipSerialM m) where- pure = ZipSerialM . K.repeat- (<*>) = K.zipWith id--FOLDABLE_INSTANCE(ZipSerialM)-TRAVERSABLE_INSTANCE(ZipSerialM)----------------------------------------------------------------------------------- Parallel Zipping----------------------------------------------------------------------------------- | Like 'zipWith' but zips concurrently i.e. both the streams being zipped--- are generated concurrently.------ @since 0.1.0-{-# INLINABLE zipAsyncWith #-}-zipAsyncWith :: (IsStream t, MonadAsync m)- => (a -> b -> c) -> t m a -> t m b -> t m c-zipAsyncWith f m1 m2 = mkStream $ \st stp sng yld -> do- ma <- mkAsync' (adaptState st) m1- mb <- mkAsync' (adaptState st) m2- foldStream st stp sng yld (K.zipWith f ma mb)---- | Like 'zipWithM' but zips concurrently i.e. both the streams being zipped--- are generated concurrently.------ @since 0.4.0-{-# INLINABLE zipAsyncWithM #-}-zipAsyncWithM :: (IsStream t, MonadAsync m)- => (a -> b -> m c) -> t m a -> t m b -> t m c-zipAsyncWithM f m1 m2 = mkStream $ \st stp sng yld -> do- ma <- mkAsync' (adaptState st) m1- mb <- mkAsync' (adaptState st) m2- foldStream st stp sng yld (K.zipWithM f ma mb)----------------------------------------------------------------------------------- Parallely Zipping Streams------------------------------------------------------------------------------------- | Like 'ZipSerialM' but zips in parallel, it generates all the elements to--- be zipped concurrently.------ @--- main = (toList . 'zipAsyncly' $ (,,) \<$\> s1 \<*\> s2 \<*\> s3) >>= print--- where s1 = fromFoldable [1, 2]--- s2 = fromFoldable [3, 4]--- s3 = fromFoldable [5, 6]--- @--- @--- [(1,3,5),(2,4,6)]--- @------ The 'Semigroup' instance of this type works the same way as that of--- 'SerialT'.------ @since 0.2.0-newtype ZipAsyncM m a = ZipAsyncM {getZipAsyncM :: Stream m a}- deriving (Semigroup, Monoid)---- | An IO stream whose applicative instance zips streams wAsyncly.------ @since 0.2.0-type ZipAsync = ZipAsyncM IO---- | Fix the type of a polymorphic stream as 'ZipAsyncM'.------ @since 0.2.0-zipAsyncly :: IsStream t => ZipAsyncM m a -> t m a-zipAsyncly = K.adapt---- | Same as 'zipAsyncly'.------ @since 0.1.0-{-# DEPRECATED zippingAsync "Please use zipAsyncly instead." #-}-zippingAsync :: IsStream t => ZipAsyncM m a -> t m a-zippingAsync = zipAsyncly--consMZipAsync :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a-consMZipAsync m ms = fromStream $ K.consMStream m (toStream ms)--instance IsStream ZipAsyncM where- toStream = getZipAsyncM- fromStream = ZipAsyncM-- {-# INLINE consM #-}- {-# SPECIALIZE consM :: IO a -> ZipAsyncM IO a -> ZipAsyncM IO a #-}- consM :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a- consM = consMZipAsync-- {-# INLINE (|:) #-}- {-# SPECIALIZE (|:) :: IO a -> ZipAsyncM IO a -> ZipAsyncM IO a #-}- (|:) :: Monad m => m a -> ZipAsyncM m a -> ZipAsyncM m a- (|:) = consMZipAsync--instance Monad m => Functor (ZipAsyncM m) where- fmap = map--instance MonadAsync m => Applicative (ZipAsyncM m) where- pure = ZipAsyncM . K.repeat- m1 <*> m2 = zipAsyncWith id m1 m2
− src/Streamly/Streams/inline.hs
@@ -1,27 +0,0 @@--- We use fromStreamK/toStreamK to convert the direct style stream to CPS--- style. In the first phase we try fusing the fromStreamK/toStreamK using:------ {-# RULES "fromStreamK/toStreamK fusion"--- forall s. toStreamK (fromStreamK s) = s #-}------ If for some reason some of the operations could not be fused then we have--- fallback rules in the second phase. For example:------ {-# INLINE_EARLY unfoldr #-}--- unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a--- unfoldr step seed = fromStreamS (S.unfoldr step seed)--- {-# RULES "unfoldr fallback to StreamK" [1]--- forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}```------ Then, fromStreamK/toStreamK are inlined in the last phase:------ {-# INLINE_LATE toStreamK #-}--- toStreamK :: Monad m => Stream m a -> K.Stream m a```------ The fallback rules make sure that if we could not fuse the direct style--- operations then better use the CPS style operation, because unfused direct--- style would have worse performance than the CPS style ops.--#define INLINE_EARLY INLINE [2]-#define INLINE_NORMAL INLINE [1]-#define INLINE_LATE INLINE [0]
+ src/inline.hs view
@@ -0,0 +1,27 @@+-- We use fromStreamK/toStreamK to convert the direct style stream to CPS+-- style. In the first phase we try fusing the fromStreamK/toStreamK using:+--+-- {-# RULES "fromStreamK/toStreamK fusion"+-- forall s. toStreamK (fromStreamK s) = s #-}+--+-- If for some reason some of the operations could not be fused then we have+-- fallback rules in the second phase. For example:+--+-- {-# INLINE_EARLY unfoldr #-}+-- unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a+-- unfoldr step seed = fromStreamS (S.unfoldr step seed)+-- {-# RULES "unfoldr fallback to StreamK" [1]+-- forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}```+--+-- Then, fromStreamK/toStreamK are inlined in the last phase:+--+-- {-# INLINE_LATE toStreamK #-}+-- toStreamK :: Monad m => Stream m a -> K.Stream m a```+--+-- The fallback rules make sure that if we could not fuse the direct style+-- operations then better use the CPS style operation, because unfused direct+-- style would have worse performance than the CPS style ops.++#define INLINE_EARLY INLINE [2]+#define INLINE_NORMAL INLINE [1]+#define INLINE_LATE INLINE [0]
stack.yaml view
@@ -1,4 +1,4 @@-resolver: lts-13.25+resolver: lts-14.25 packages: - '.' extra-deps:@@ -7,6 +7,8 @@ - Chart-diagrams-1.9.2 - bench-show-0.2.2 - inspection-testing-0.4.2.1+ - fusion-plugin-types-0.1.0+ - fusion-plugin-0.2.0 #allow-newer: true flags: {}
streamly.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: streamly-version: 0.7.0+version: 0.7.1 synopsis: Beautiful Streaming, Concurrent and Reactive Composition description: Streamly is a framework for writing programs in a high level, declarative@@ -20,7 +20,7 @@ it may not be for you. It expresses a small "hello world" program with the same efficiency, simplicity and elegance as a large scale concurrent application. It unifies many different aspects of special purpose libraries- into a single yet simple framework. + into a single yet simple framework. . Streamly covers the functionality provided by Haskell lists as well as the functionality provided by streaming libraries like@@ -62,7 +62,7 @@ . Where to find more information: .- * /Quick Overview/: <src/README.md README file> in the package+ * /Quick Overview/: <#readme README file> in the package * /Building/: <src/docs/Build.md Build guide> for optimal performance * /Detailed Tutorial/: "Streamly.Tutorial" module in the haddock documentation * /Interoperation/: "Streamly.Tutorial" module for interop with other@@ -75,10 +75,6 @@ * <https://github.com/composewell/streaming-benchmarks Streaming Benchmarks> * <https://github.com/composewell/concurrency-benchmarks Concurrency Benchmarks> .- For additional unreleased/experimental APIs, build the haddock docs using:- .- > $ cabal haddock --haddock-option="--show-all"- > $ stack haddock --haddock-arguments "--show-all" --no-haddock-deps homepage: https://github.com/composewell/streamly@@ -101,11 +97,11 @@ Changelog.md credits/*.md credits/base-4.12.0.0.txt+ credits/primitive-0.7.0.0.txt credits/bjoern-2008-2009.txt credits/clock-0.7.2.txt credits/foldl-1.4.5.txt credits/pipes-concurrency-2.0.8.txt- credits/pipes-concurrency.txt credits/transient-0.5.5.txt credits/vector-0.12.0.2.txt credits/Yampa-0.10.6.2.txt@@ -113,10 +109,13 @@ docs/streamly-vs-async.md docs/streamly-vs-lists.md docs/transformers.md+ docs/Build.md+ design/*.md+ design/*.png bench.sh stack.yaml- src/Streamly/Streams/Instances.hs- src/Streamly/Streams/inline.hs+ src/Streamly/Internal/Data/Stream/Instances.hs+ src/inline.hs configure.ac configure src/Streamly/Internal/Data/Time/config.h.in@@ -131,8 +130,8 @@ type: git location: https://github.com/composewell/streamly -flag benchmark- description: Benchmark build+flag fusion-plugin+ description: Use fusion plugin for benchmarks and executables manual: True default: False @@ -152,17 +151,17 @@ default: False flag has-llvm- description: Use llvm backend for better performance+ description: Use llvm backend for code generation manual: True default: False flag no-charts- description: Disable chart generation+ description: Disable benchmark charts in development build manual: True default: False flag no-fusion- description: Disable rewrite rules+ description: Disable rewrite rules for stream fusion manual: True default: False @@ -222,6 +221,7 @@ common optimization-options ghc-options: -O2+ -fdicts-strict -fspec-constr-recursive=16 -fmax-worker-args=16 @@ -243,18 +243,44 @@ -- in general. common test-options import: compile-options, threading-options+ ghc-options: -O0 -fno-ignore-asserts+ if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6)+ ghc-options: -fplugin Fusion.Plugin+ build-depends:+ fusion-plugin >= 0.2 && < 0.3 -- Used by maxrate test, benchmarks and executables common exe-options import: compile-options, optimization-options, threading-options+ if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6)+ ghc-options: -fplugin Fusion.Plugin+ build-depends:+ fusion-plugin >= 0.2 && < 0.3 -- Some benchmarks are threaded some are not+-- XXX dependencies should be separated under bench-depends common bench-options import: compile-options, optimization-options- ghc-options: -with-rtsopts "-T"+ ghc-options: -with-rtsopts "-T -K32K -M16M"+ if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6)+ ghc-options: -fplugin Fusion.Plugin+ build-depends:+ fusion-plugin >= 0.2 && < 0.3+ build-depends: mtl >= 2.2 && < 3 +common bench-options-threaded+ import: compile-options, optimization-options+ -- -threaded and -N2 is important because some GC and space leak issues+ -- trigger only with these options.+ ghc-options: -threaded -with-rtsopts "-T -N2 -K32K -M16M"+ if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6)+ ghc-options: -fplugin Fusion.Plugin+ build-depends:+ fusion-plugin >= 0.2 && < 0.3+ build-depends: mtl >= 2.2 && < 3+ ------------------------------------------------------------------------------- -- Library -------------------------------------------------------------------------------@@ -263,40 +289,27 @@ import: lib-options js-sources: jsbits/clock.js include-dirs: src/Streamly/Internal/Data/Time- , src/Streamly/Streams+ , src if os(windows) c-sources: src/Streamly/Internal/Data/Time/Windows.c if os(darwin) c-sources: src/Streamly/Internal/Data/Time/Darwin.c hs-source-dirs: src other-modules:+ Streamly.Data.Array+ , Streamly.Data.SmallArray+ , Streamly.Data.Prim.Array+ -- Memory storage Streamly.Memory.Malloc , Streamly.Memory.Ring - -- Base streams- , Streamly.Streams.StreamK.Type- , Streamly.Streams.StreamK- , Streamly.Streams.StreamDK.Type- , Streamly.Streams.StreamDK- , Streamly.Streams.StreamD- , Streamly.Streams.Enumeration- , Streamly.Streams.Prelude-- -- Higher level streams- , Streamly.Streams.SVar- , Streamly.Streams.Serial- , Streamly.Streams.Async- , Streamly.Streams.Parallel- , Streamly.Streams.Ahead- , Streamly.Streams.Zip- , Streamly.Streams.Combinators- , Streamly.FileSystem.IOVec , Streamly.FileSystem.FDIO , Streamly.FileSystem.FD - exposed-modules: Streamly.Prelude+ exposed-modules:+ Streamly.Prelude , Streamly , Streamly.Data.Fold , Streamly.Data.Unfold@@ -309,13 +322,19 @@ , Streamly.Tutorial -- Internal modules+ , Streamly.Internal.BaseCompat+ , Streamly.Internal.Control.Monad , Streamly.Internal.Data.Strict , Streamly.Internal.Data.Atomics , Streamly.Internal.Data.Time , Streamly.Internal.Data.Time.Units , Streamly.Internal.Data.Time.Clock- , Streamly.Internal.Data.Stream.StreamD.Type , Streamly.Internal.Data.SVar+ , Streamly.Internal.Data.Array+ , Streamly.Internal.Data.Prim.Array.Types+ , Streamly.Internal.Data.Prim.Array+ , Streamly.Internal.Data.SmallArray.Types+ , Streamly.Internal.Data.SmallArray , Streamly.Internal.Memory.Array.Types , Streamly.Internal.Memory.Array , Streamly.Internal.Memory.ArrayStream@@ -323,6 +342,25 @@ , Streamly.Internal.Data.Fold , Streamly.Internal.Data.Sink.Types , Streamly.Internal.Data.Sink++ -- Base streams+ , Streamly.Internal.Data.Stream.StreamK.Type+ , Streamly.Internal.Data.Stream.StreamK+ , Streamly.Internal.Data.Stream.StreamD.Type+ , Streamly.Internal.Data.Stream.StreamD+ , Streamly.Internal.Data.Stream.StreamDK.Type+ , Streamly.Internal.Data.Stream.StreamDK+ , Streamly.Internal.Data.Stream.Enumeration+ , Streamly.Internal.Data.Stream.Prelude++ -- Higher level streams+ , Streamly.Internal.Data.Stream.SVar+ , Streamly.Internal.Data.Stream.Serial+ , Streamly.Internal.Data.Stream.Async+ , Streamly.Internal.Data.Stream.Parallel+ , Streamly.Internal.Data.Stream.Ahead+ , Streamly.Internal.Data.Stream.Zip+ , Streamly.Internal.Data.Stream.Combinators , Streamly.Internal.Data.Unfold.Types , Streamly.Internal.Data.Unfold , Streamly.Internal.Data.Pipe.Types@@ -335,6 +373,10 @@ , Streamly.Internal.Data.Unicode.Char , Streamly.Internal.Memory.Unicode.Array , Streamly.Internal.Prelude++ -- Mutable data+ , Streamly.Internal.Mutable.Prim.Var+ if !impl(ghcjs) exposed-modules: Streamly.Network.Socket@@ -343,41 +385,48 @@ , Streamly.Internal.Network.Socket , Streamly.Internal.Network.Inet.TCP - if flag(benchmark)- exposed-modules:- Streamly.Benchmark.FileIO.Array- , Streamly.Benchmark.FileIO.Stream- , Streamly.Benchmark.Prelude-- build-depends: base >= 4.8 && < 5- , ghc-prim >= 0.2 && < 0.6+ build-depends:+ -- Core libraries shipped with ghc, the min and max+ -- constraints of these libraries should match with+ -- the GHC versions we support+ base >= 4.8 && < 5+ , containers >= 0.5 && < 0.7 , deepseq >= 1.4.1 && < 1.5- , containers >= 0.5.8.2 && < 0.7- , heaps >= 0.3 && < 0.4- , directory >= 1.3 && < 1.4+ , directory >= 1.2.2 && < 1.4+ , exceptions >= 0.8 && < 0.11+ , ghc-prim >= 0.2 && < 0.6+ , mtl >= 2.2 && < 3+ , primitive >= 0.5.4 && < 0.8+ , transformers >= 0.4 && < 0.6 + , heaps >= 0.3 && < 0.4+ -- concurrency , atomic-primops >= 0.8 && < 0.9 , lockfree-queue >= 0.2.3 && < 0.3 -- transfomers- , exceptions >= 0.8 && < 0.11 , monad-control >= 1.0 && < 2- , mtl >= 2.2 && < 3- , transformers >= 0.4 && < 0.6 , transformers-base >= 0.4 && < 0.5 - if flag(inspection)- build-depends: template-haskell >= 2.14 && < 2.16- , inspection-testing >= 0.4 && < 0.5+ , fusion-plugin-types >= 0.1 && < 0.2 if !impl(ghcjs) build-depends:- network >= 2.6 && < 4+ network >= 2.6 && < 4 if impl(ghc < 8.0) build-depends:- semigroups >= 0.18 && < 0.19+ semigroups >= 0.18 && < 0.19 + if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5++ -- Array uses a Storable constraint in dev build making several inspection+ -- tests fail+ if flag(dev) && flag(inspection)+ build-depends: inspection-and-dev-flags-cannot-be-used-together+ ------------------------------------------------------------------------------- -- Test suites -------------------------------------------------------------------------------@@ -392,12 +441,25 @@ streamly , base >= 4.8 && < 5 , hspec >= 2.0 && < 3- , containers >= 0.5.8.2 && < 0.7+ , containers >= 0.5 && < 0.7 , transformers >= 0.4 && < 0.6 , mtl >= 2.2 && < 3 , exceptions >= 0.8 && < 0.11 default-language: Haskell2010 +test-suite internal-prelude-test+ import: test-options+ type: exitcode-stdio-1.0+ main-is: Streamly/Test/Internal/Prelude.hs+ js-sources: jsbits/clock.js+ hs-source-dirs: test+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , QuickCheck >= 2.10 && < 2.14+ , hspec >= 2.0 && < 3+ default-language: Haskell2010+ test-suite pure-streams-base import: test-options type: exitcode-stdio-1.0@@ -440,9 +502,10 @@ test-suite array-test import: test-options type: exitcode-stdio-1.0- main-is: Arrays.hs+ main-is: Streamly/Test/Array.hs js-sources: jsbits/clock.js hs-source-dirs: test+ cpp-options: -DTEST_ARRAY build-depends: streamly , base >= 4.8 && < 5@@ -453,6 +516,69 @@ transformers >= 0.4 && < 0.6 default-language: Haskell2010 +test-suite internal-data-fold-test+ import: test-options+ type: exitcode-stdio-1.0+ main-is: Streamly/Test/Internal/Data/Fold.hs+ js-sources: jsbits/clock.js+ hs-source-dirs: test+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , hspec >= 2.0 && < 3+ , QuickCheck >= 2.10 && < 2.14+ default-language: Haskell2010++test-suite data-array-test+ import: test-options+ type: exitcode-stdio-1.0+ main-is: Streamly/Test/Array.hs+ js-sources: jsbits/clock.js+ hs-source-dirs: test+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , QuickCheck >= 2.10 && < 2.14+ , hspec >= 2.0 && < 3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ default-language: Haskell2010++test-suite smallarray-test+ import: test-options+ type: exitcode-stdio-1.0+ main-is: Streamly/Test/Array.hs+ js-sources: jsbits/clock.js+ hs-source-dirs: test+ cpp-options: -DTEST_SMALL_ARRAY+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , QuickCheck >= 2.10 && < 2.14+ , hspec >= 2.0 && < 3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ default-language: Haskell2010++test-suite primarray-test+ import: test-options+ type: exitcode-stdio-1.0+ main-is: Streamly/Test/Array.hs+ js-sources: jsbits/clock.js+ hs-source-dirs: test+ cpp-options: -DTEST_PRIM_ARRAY+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , QuickCheck >= 2.10 && < 2.14+ , hspec >= 2.0 && < 3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ default-language: Haskell2010+ test-suite string-test import: test-options type: exitcode-stdio-1.0@@ -519,74 +645,89 @@ , base >= 4.8 && < 5 , random >= 1.0.0 && < 2 +test-suite version-bounds+ import: test-options+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ main-is: version-bounds.hs+ hs-source-dirs: test+ build-Depends:+ streamly+ , ghc+ , base >= 4.8 && < 5+ ------------------------------------------------------------------------------- -- Benchmarks ------------------------------------------------------------------------------- +-- For linear, linear-async, linear-rate, nested and nested-concurrent+-- you can pass the number of elements in the stream using the+-- --stream-size option:+-- $ cabal run linear -- --stream-size 1000000+ benchmark linear import: bench-options type: exitcode-stdio-1.0 hs-source-dirs: benchmark+ -- XXX heap/stack limits can be reduced once we split out the buffered+ -- benchmarks into a separate suite+ ghc-options: -with-rtsopts "-T -K4M -M128M" main-is: Linear.hs- if flag(benchmark)- buildable: True+ other-modules: Streamly.Benchmark.Prelude, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0) build-depends:- streamly- , base >= 4.8 && < 5- , deepseq >= 1.4.1 && < 1.5- , random >= 1.0 && < 2.0- , gauge >= 0.2.4 && < 0.3- if impl(ghc < 8.0)- build-depends:- transformers >= 0.4 && < 0.6- else- buildable: False+ transformers >= 0.4 && < 0.6+ if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5 -benchmark linear-async+benchmark nested import: bench-options- cpp-options: -DLINEAR_ASYNC type: exitcode-stdio-1.0 hs-source-dirs: benchmark- main-is: LinearAsync.hs- if flag(benchmark)- buildable: True+ ghc-options: -with-rtsopts "-T -K256K -M16M"+ main-is: Nested.hs+ other-modules: NestedOps, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0) build-depends:- streamly- , base >= 4.8 && < 5- , deepseq >= 1.4.1 && < 1.5- , random >= 1.0 && < 2.0- , gauge >= 0.2.4 && < 0.3- if impl(ghc < 8.0)- build-depends:- transformers >= 0.4 && < 0.6- else- buildable: False+ transformers >= 0.4 && < 0.6 -benchmark linear-rate+benchmark nested-unfold import: bench-options type: exitcode-stdio-1.0 hs-source-dirs: benchmark- main-is: LinearRate.hs- if flag(benchmark)- buildable: True+ ghc-options: -with-rtsopts "-T -K64K -M16M"+ main-is: NestedUnfold.hs+ other-modules: NestedUnfoldOps, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0) build-depends:- streamly- , base >= 4.8 && < 5- , deepseq >= 1.4.1 && < 1.5- , random >= 1.0 && < 2.0- , gauge >= 0.2.4 && < 0.3- if impl(ghc < 8.0)- build-depends:- transformers >= 0.4 && < 0.6- else- buildable: False+ transformers >= 0.4 && < 0.6 -benchmark nested+benchmark unpinned-array import: bench-options type: exitcode-stdio-1.0 hs-source-dirs: benchmark- main-is: Nested.hs- other-modules: NestedOps+ ghc-options: -with-rtsopts "-T -K1K -M128M"+ main-is: Streamly/Benchmark/Data/Array.hs+ other-modules: Streamly.Benchmark.Data.ArrayOps build-depends: streamly , base >= 4.8 && < 5@@ -597,12 +738,13 @@ build-depends: transformers >= 0.4 && < 0.6 -benchmark nestedUnfold+benchmark prim-array import: bench-options type: exitcode-stdio-1.0 hs-source-dirs: benchmark- main-is: NestedUnfold.hs- other-modules: NestedUnfoldOps+ ghc-options: -with-rtsopts "-T -K64K -M32M"+ main-is: Streamly/Benchmark/Data/Prim/Array.hs+ other-modules: Streamly.Benchmark.Data.Prim.ArrayOps build-depends: streamly , base >= 4.8 && < 5@@ -613,10 +755,28 @@ build-depends: transformers >= 0.4 && < 0.6 +benchmark small-array+ import: bench-options+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmark+ ghc-options: -with-rtsopts "-T -K128K -M16M"+ main-is: Streamly/Benchmark/Data/SmallArray.hs+ other-modules: Streamly.Benchmark.Data.SmallArrayOps+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ benchmark array import: bench-options type: exitcode-stdio-1.0 hs-source-dirs: benchmark+ ghc-options: -with-rtsopts "-T -K64K -M128M" main-is: Array.hs other-modules: ArrayOps build-depends:@@ -637,150 +797,163 @@ -- ghc-options: -funfolding-use-threshold=150 hs-source-dirs: benchmark main-is: FileIO.hs- if flag(benchmark)- buildable: True+ other-modules: Streamly.Benchmark.FileIO.Array+ , Streamly.Benchmark.FileIO.Stream+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , gauge >= 0.2.4 && < 0.3+ , typed-process >= 0.2.3 && < 0.3+ , deepseq >= 1.4.1 && < 1.5+ if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5++-------------------------------------------------------------------------------+-- Threaded Benchmarks+-------------------------------------------------------------------------------++benchmark linear-async+ import: bench-options-threaded+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmark+ ghc-options: -with-rtsopts "-T -N2 -K64K -M16M"+ main-is: LinearAsync.hs+ other-modules: Streamly.Benchmark.Prelude, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0) build-depends:- streamly- , base >= 4.8 && < 5- , gauge >= 0.2.4 && < 0.3- , typed-process >= 0.2.3 && < 0.3- , deepseq >= 1.4.1 && < 1.5- else- buildable: False+ transformers >= 0.4 && < 0.6+ if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5 +benchmark nested-concurrent+ import: bench-options-threaded+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmark+ -- XXX this can be lowered once we split out the finite benchmarks+ ghc-options: -with-rtsopts "-T -N2 -K256K -M128M"+ main-is: NestedConcurrent.hs+ other-modules: NestedOps, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6++benchmark parallel+ import: bench-options-threaded+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmark+ ghc-options: -with-rtsopts "-T -N2 -K128K -M256M"+ main-is: Parallel.hs+ other-modules: Streamly.Benchmark.Prelude, NestedOps, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5++benchmark linear-rate+ import: bench-options-threaded+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmark+ main-is: LinearRate.hs+ other-modules: Streamly.Benchmark.Prelude, Common+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , deepseq >= 1.4.1 && < 1.5+ , random >= 1.0 && < 2.0+ , gauge >= 0.2.4 && < 0.3+ if impl(ghc < 8.0)+ build-depends:+ transformers >= 0.4 && < 0.6+ if flag(inspection)+ build-depends: template-haskell >= 2.14 && < 2.16+ , inspection-testing >= 0.4 && < 0.5+ benchmark concurrent- import: bench-options+ import: bench-options-threaded type: exitcode-stdio-1.0 hs-source-dirs: benchmark main-is: Concurrent.hs- if flag(dev)- buildable: True- build-depends:- streamly- , base >= 4.8 && < 5- , gauge >= 0.2.4 && < 0.3- else- buildable: False+ ghc-options: -with-rtsopts "-T -N2 -K256K -M384M"+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , gauge >= 0.2.4 && < 0.3 ---------------------------------------------------------------------------------- Internal benchmarks for unexposed modules+-- Internal benchmarks ------------------------------------------------------------------------------- --- We have to copy the streamly library modules here because there is no--- way to use unexposed modules from the library.- benchmark base import: bench-options type: exitcode-stdio-1.0- include-dirs: src/Streamly/Internal/Data/Time- , src/Streamly/Streams- if os(windows)- c-sources: src/Streamly/Internal/Data/Time/Windows.c- if os(darwin)- c-sources: src/Streamly/Internal/Data/Time/Darwin.c- hs-source-dirs: benchmark, src+ hs-source-dirs: benchmark+ if flag(dev)+ cpp-options: -DDEVBUILD+ ghc-options: -with-rtsopts "-T -K2M -M16M"+ else+ ghc-options: -with-rtsopts "-T -K128K -M16M" main-is: BaseStreams.hs- other-modules: Streamly.Internal.Data.Atomics- , Streamly.Internal.Data.Stream.StreamD.Type- , Streamly.Internal.Data.SVar- , Streamly.Internal.Data.Time.Units- , Streamly.Internal.Data.Time.Clock- , Streamly.Streams.StreamDK.Type- , Streamly.Streams.StreamDK- , Streamly.Streams.StreamK.Type- , Streamly.Streams.StreamK- , Streamly.Streams.StreamD- , Streamly.Streams.Prelude- , Streamly.FileSystem.IOVec-- , StreamDOps+ other-modules: StreamDOps , StreamKOps , StreamDKOps - if flag(dev)- buildable: True- build-depends:- base >= 4.8 && < 5+ build-depends:+ streamly+ , base >= 4.8 && < 5 , deepseq >= 1.4.1 && < 1.5 , random >= 1.0 && < 2.0 , gauge >= 0.2.4 && < 0.3 - , ghc-prim >= 0.2 && < 0.6- , containers >= 0.5.8.2 && < 0.7- , heaps >= 0.3 && < 0.4-- -- concurrency- , atomic-primops >= 0.8 && < 0.9- , lockfree-queue >= 0.2.3 && < 0.3-- , exceptions >= 0.8 && < 0.11- , monad-control >= 1.0 && < 2- , mtl >= 2.2 && < 3- , transformers >= 0.4 && < 0.6- , transformers-base >= 0.4 && < 0.5-- if impl(ghc < 8.0)- build-depends:- semigroups >= 0.18 && < 0.19- else- buildable: False- executable nano-bench import: bench-options- hs-source-dirs: benchmark, src- include-dirs: src/Streamly/Internal/Data/Time- , src/Streamly/Streams- if os(windows)- c-sources: src/Streamly/Internal/Data/Time/Windows.c- if os(darwin)- c-sources: src/Streamly/Internal/Data/Time/Darwin.c+ hs-source-dirs: benchmark main-is: NanoBenchmarks.hs- other-modules: Streamly.Internal.Data.Atomics- , Streamly.Internal.Data.Stream.StreamD.Type- , Streamly.Internal.Data.SVar- , Streamly.Internal.Data.Time.Units- , Streamly.Internal.Data.Time.Clock- , Streamly.Streams.StreamK.Type- , Streamly.Streams.StreamK- , Streamly.FileSystem.IOVec- , Streamly.Streams.StreamD if flag(dev) buildable: True build-depends:- base >= 4.8 && < 5- , gauge >= 0.2.4 && < 0.3- , ghc-prim >= 0.2 && < 0.6- , containers >= 0.5.8.2 && < 0.7- , deepseq >= 1.4.1 && < 1.5- , heaps >= 0.3 && < 0.4- , random >= 1.0 && < 2.0-- -- concurrency- , atomic-primops >= 0.8 && < 0.9- , lockfree-queue >= 0.2.3 && < 0.3-- , exceptions >= 0.8 && < 0.11- , monad-control >= 1.0 && < 2- , mtl >= 2.2 && < 3- , transformers >= 0.4 && < 0.6- , transformers-base >= 0.4 && < 0.5+ streamly+ , base >= 4.8 && < 5+ , gauge >= 0.2.4 && < 0.3+ , random >= 1.0 && < 2.0 else buildable: False -executable adaptive- import: bench-options+benchmark adaptive+ import: bench-options-threaded+ type: exitcode-stdio-1.0 hs-source-dirs: benchmark main-is: Adaptive.hs default-language: Haskell2010- if flag(dev)- buildable: True+ build-depends:+ streamly+ , base >= 4.8 && < 5+ , gauge >= 0.2.4 && < 0.3+ , random >= 1.0 && < 2.0+ if impl(ghc < 8.0) build-depends:- streamly- , base >= 4.8 && < 5- , gauge >= 0.2.4 && < 0.3- , random >= 1.0 && < 2.0- else- buildable: False+ transformers >= 0.4 && < 0.6 executable chart default-language: Haskell2010@@ -823,7 +996,6 @@ build-Depends: streamly , base >= 4.8 && < 5- , directory >= 1.3 && < 1.4 if impl(ghc < 8.0) build-depends: transformers >= 0.4 && < 0.6
− test/Arrays.hs
@@ -1,115 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}--module Main (main) where--import Foreign.Storable (Storable(..))--import Test.Hspec.QuickCheck-import Test.QuickCheck (Property, forAll, Gen, vectorOf, arbitrary, choose)-import Test.QuickCheck.Monadic (monadicIO, assert)--import Test.Hspec as H--import qualified Streamly.Internal.Memory.Array as IA-import qualified Streamly.Memory.Array as A-import qualified Streamly.Prelude as S-import qualified Streamly.Internal.Prelude as IP---- Coverage build takes too long with default number of tests-maxTestCount :: Int-#ifdef DEVBUILD-maxTestCount = 100-#else-maxTestCount = 10-#endif--allocOverhead :: Int-allocOverhead = 2 * sizeOf (undefined :: Int)---- XXX this should be in sync with the defaultChunkSize in Array code, or we--- should expose that and use that. For fast testing we could reduce the--- defaultChunkSize under CPP conditionals.----defaultChunkSize :: Int-defaultChunkSize = 32 * k - allocOverhead- where k = 1024--maxArrLen :: Int-maxArrLen = defaultChunkSize * 8--testLength :: Property-testLength =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- arr <- S.fold (A.writeN len)- $ S.fromList list- assert (A.length arr == len)--testFromToStreamN :: Property-testFromToStreamN =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- arr <- S.fold (A.writeN len)- $ S.fromList list- xs <- S.toList- $ S.unfold A.read arr- assert (xs == list)--testToStreamRev :: Property-testToStreamRev =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- arr <- S.fold (A.writeN len)- $ S.fromList list- xs <- S.toList- $ IA.toStreamRev arr- assert (xs == reverse list)--testArraysOf :: Property-testArraysOf =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- xs <- S.toList- $ S.concatUnfold A.read- $ IP.arraysOf 240- $ S.fromList list- assert (xs == list)--testFlattenArrays :: Property-testFlattenArrays =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- xs <- S.toList- $ S.concatUnfold A.read- $ IP.arraysOf 240- $ S.fromList list- assert (xs == list)--testFromToStream :: Property-testFromToStream =- forAll (choose (0, maxArrLen)) $ \len ->- forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->- monadicIO $ do- arr <- S.fold A.write $ S.fromList list- xs <- S.toList- $ S.unfold A.read arr- assert (xs == list)--main :: IO ()-main = hspec- $ H.parallel- $ modifyMaxSuccess (const maxTestCount)- $ do- describe "Construction" $ do- prop "length . writeN n === n" $ testLength- prop "read . writeN n === id" $ testFromToStreamN- prop "toStreamRev . write === reverse" $ testToStreamRev- prop "arraysOf concats to original" $ testArraysOf- prop "concats to original" $ testFlattenArrays- prop "read . write === id" $ testFromToStream
test/Prop.hs view
@@ -34,6 +34,7 @@ import Streamly as S import qualified Streamly.Prelude as S import qualified Streamly.Data.Fold as FL+import qualified Streamly.Internal.Data.Fold as FL -- Coverage build takes too long with default number of tests maxTestCount :: Int@@ -492,6 +493,22 @@ let f x = if odd x then Just (x + 100) else Nothing prop (desc <> " mapMaybe") $ transform (mapMaybe f) t (S.mapMaybe f) + -- tap+ prop (desc <> " tap FL.sum . map (+1)") $ \a b ->+ withMaxSuccess maxTestCount $+ monadicIO $ do+ cref <- run $ newIORef 0+ let sumfoldinref = FL.Fold (\_ e -> modifyIORef' cref (e+))+ (return ())+ (const $ return ())+ op = S.tap sumfoldinref . S.mapM (\x -> return (x+1))+ listOp = fmap (+1)+ stream <- run ((S.toList . t) $ op (constr a <> constr b))+ let list = listOp (a <> b)+ ssum <- run $ readIORef cref+ assert (sum list == ssum)+ listEquals eq stream list+ -- reordering prop (desc <> " reverse") $ transform reverse t S.reverse -- prop (desc <> " reverse'") $ transform reverse t S.reverse'@@ -620,6 +637,10 @@ prop (desc <> " findIndices") $ transform (findIndices odd) t (S.findIndices odd)+ prop (desc <> " findIndices . filter") $+ transform (findIndices odd . filter odd)+ t+ (S.findIndices odd . S.filter odd) prop (desc <> " elemIndices") $ transform (elemIndices 0) t (S.elemIndices 0)
+ test/Streamly/Test/Array.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE CPP #-}++-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Main (main) where++import Foreign.Storable (Storable(..))++import Test.Hspec.QuickCheck+import Test.QuickCheck (Property, forAll, Gen, vectorOf, arbitrary, choose)+import Test.QuickCheck.Monadic (monadicIO, assert, run)++import Test.Hspec as H++import Streamly (SerialT)++import qualified Streamly.Prelude as S++#ifdef TEST_SMALL_ARRAY+import qualified Streamly.Internal.Data.SmallArray as A+type Array = A.SmallArray+#elif defined(TEST_ARRAY)+import qualified Streamly.Memory.Array as A+import qualified Streamly.Internal.Memory.Array as A+import qualified Streamly.Internal.Prelude as IP+type Array = A.Array+#elif defined(TEST_PRIM_ARRAY)+import qualified Streamly.Internal.Data.Prim.Array as A+type Array = A.PrimArray+#else+import qualified Streamly.Internal.Data.Array as A+type Array = A.Array+#endif++-- Coverage build takes too long with default number of tests+maxTestCount :: Int+#ifdef DEVBUILD+maxTestCount = 100+#else+maxTestCount = 10+#endif++allocOverhead :: Int+allocOverhead = 2 * sizeOf (undefined :: Int)++-- XXX this should be in sync with the defaultChunkSize in Array code, or we+-- should expose that and use that. For fast testing we could reduce the+-- defaultChunkSize under CPP conditionals.+--+defaultChunkSize :: Int+defaultChunkSize = 32 * k - allocOverhead+ where k = 1024++maxArrLen :: Int+maxArrLen = defaultChunkSize * 8++genericTestFrom ::+ (Int -> SerialT IO Int -> IO (Array Int))+ -> Property+genericTestFrom arrFold =+ forAll (choose (0, maxArrLen)) $ \len ->+ forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->+ monadicIO $ do+ arr <- run $ arrFold len $ S.fromList list+ assert (A.length arr == len)++testLength :: Property+testLength = genericTestFrom (\n -> S.fold (A.writeN n))++testLengthFromStreamN :: Property+testLengthFromStreamN = genericTestFrom A.fromStreamN++#ifndef TEST_SMALL_ARRAY+testLengthFromStream :: Property+testLengthFromStream = genericTestFrom (const A.fromStream)+#endif++genericTestFromTo ::+ (Int -> SerialT IO Int -> IO (Array Int))+ -> (Array Int -> SerialT IO Int)+ -> ([Int] -> [Int] -> Bool)+ -> Property+genericTestFromTo arrFold arrUnfold listEq =+ forAll (choose (0, maxArrLen)) $ \len ->+ forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->+ monadicIO $ do+ arr <- run $ arrFold len $ S.fromList list+ xs <- run $ S.toList $ arrUnfold arr+ assert (listEq xs list)++testFoldNUnfold :: Property+testFoldNUnfold =+ genericTestFromTo (\n -> S.fold (A.writeN n)) (S.unfold A.read) (==)++testFoldNToStream :: Property+testFoldNToStream =+ genericTestFromTo (\n -> S.fold (A.writeN n)) A.toStream (==)++testFoldNToStreamRev :: Property+testFoldNToStreamRev =+ genericTestFromTo+ (\n -> S.fold (A.writeN n))+ A.toStreamRev+ (\xs list -> xs == reverse list)++testFromStreamNUnfold :: Property+testFromStreamNUnfold = genericTestFromTo A.fromStreamN (S.unfold A.read) (==)++testFromStreamNToStream :: Property+testFromStreamNToStream = genericTestFromTo A.fromStreamN A.toStream (==)++#ifndef TEST_SMALL_ARRAY+testFromStreamToStream :: Property+testFromStreamToStream = genericTestFromTo (const A.fromStream) A.toStream (==)++testFoldUnfold :: Property+testFoldUnfold = genericTestFromTo (const (S.fold A.write)) (S.unfold A.read) (==)+#endif++#ifdef TEST_ARRAY+testArraysOf :: Property+testArraysOf =+ forAll (choose (0, maxArrLen)) $ \len ->+ forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->+ monadicIO $ do+ xs <- S.toList+ $ S.concatUnfold A.read+ $ IP.arraysOf 240+ $ S.fromList list+ assert (xs == list)++lastN :: Int -> [a] -> [a]+lastN n l = drop (length l - n) l++testLastN :: Property+testLastN =+ forAll (choose (0, maxArrLen)) $ \len ->+ forAll (choose (0, len)) $ \n ->+ forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->+ monadicIO $ do+ xs <- fmap A.toList+ $ S.fold (A.lastN n)+ $ S.fromList list+ assert (xs == lastN n list)+#endif++main :: IO ()+main =+ hspec $+ H.parallel $+ modifyMaxSuccess (const maxTestCount) $ do+ describe "Construction" $ do+ prop "length . writeN n === n" testLength+ prop "length . fromStreamN n === n" testLengthFromStreamN+ prop "read . writeN === id " testFoldNUnfold+ prop "toStream . writeN === id" testFoldNToStream+ prop "toStreamRev . writeN === reverse" testFoldNToStreamRev+ prop "read . fromStreamN === id" testFromStreamNUnfold+ prop "toStream . fromStreamN === id" testFromStreamNToStream+#ifndef TEST_SMALL_ARRAY+ prop "length . fromStream === n" testLengthFromStream+ prop "toStream . fromStream === id" testFromStreamToStream+ prop "read . write === id" testFoldUnfold+#endif+#ifdef TEST_ARRAY+ prop "arraysOf concats to original" testArraysOf+#endif+#ifdef TEST_ARRAY+ describe "Fold" $ do+ prop "lastN" $ testLastN+#endif
+ test/Streamly/Test/Internal/Data/Fold.hs view
@@ -0,0 +1,27 @@+module Main (main) where++import qualified Streamly.Prelude as S+import Streamly.Internal.Data.Fold++import Test.Hspec.QuickCheck+import Test.QuickCheck (Property, forAll, Gen, vectorOf, arbitrary, choose)+import Test.QuickCheck.Monadic (monadicIO, assert, run)++import Test.Hspec as H++maxStreamLen :: Int+maxStreamLen = 1000++testRollingHashFirstN :: Property+testRollingHashFirstN = + forAll (choose (0, maxStreamLen)) $ \len ->+ forAll (choose (0, len)) $ \n ->+ forAll (vectorOf len (arbitrary :: Gen Int)) $ \vec -> monadicIO $ do+ a <- run $ S.fold rollingHash $ S.take n $ S.fromList vec+ b <- run $ S.fold (rollingHashFirstN n) $ S.fromList vec+ assert $ a == b++main :: IO ()+main = hspec $+ describe "Rolling Hash Folds" $+ prop "testRollingHashFirstN" testRollingHashFirstN
+ test/Streamly/Test/Internal/Prelude.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- |+-- Module : Main+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Main (main) where++import Control.Concurrent (threadDelay)+import Control.Monad (when)++import Test.Hspec as H++import qualified Streamly.Prelude as S+import qualified Streamly.Internal.Data.Fold as FL+import qualified Streamly.Internal.Prelude as SI++import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)+import Streamly.Internal.Data.Time.Units+ (AbsTime, NanoSecond64(..), toRelTime64, diffAbsTime64)+import Data.Int (Int64)++tenPow8 :: Int64+tenPow8 = 10^(8 :: Int)++tenPow7 :: Int64+tenPow7 = 10^(7 :: Int)++takeDropTime :: NanoSecond64+takeDropTime = NanoSecond64 $ 5 * tenPow8++checkTakeDropTime :: (Maybe AbsTime, Maybe AbsTime) -> IO Bool+checkTakeDropTime (mt0, mt1) = do+ let graceTime = NanoSecond64 $ 8 * tenPow7+ case mt0 of+ Nothing -> return True+ Just t0 ->+ case mt1 of+ Nothing -> return True+ Just t1 -> do+ let tMax = toRelTime64 (takeDropTime + graceTime)+ let tMin = toRelTime64 (takeDropTime - graceTime)+ let t = diffAbsTime64 t1 t0+ let r = t >= tMin && t <= tMax+ when (not r) $ putStrLn $+ "t = " ++ show t +++ " tMin = " ++ show tMin +++ " tMax = " ++ show tMax+ return r++testTakeByTime :: IO Bool+testTakeByTime = do+ r <-+ S.fold ((,) <$> FL.head <*> FL.last)+ $ SI.takeByTime takeDropTime+ $ S.repeatM (threadDelay 1000 >> getTime Monotonic)+ checkTakeDropTime r++testDropByTime :: IO Bool+testDropByTime = do+ t0 <- getTime Monotonic+ mt1 <-+ S.head+ $ SI.dropByTime takeDropTime+ $ S.repeatM (threadDelay 1000 >> getTime Monotonic)+ checkTakeDropTime (Just t0, mt1)++main :: IO ()+main =+ hspec $+ describe "Filtering" $ do+ it "takeByTime" (testTakeByTime `shouldReturn` True)+ it "dropByTime" (testDropByTime `shouldReturn` True)
+ test/version-bounds.hs view
@@ -0,0 +1,4 @@+main :: IO ()+main =+ print+ "Version bounds do not conflict when both streamly and ghc are dependencies."